]> granicus.if.org Git - icu/commitdiff
Move getUnitsData to a separate file
authorYounies Mahmoud <younies.mahmoud@gmail.com>
Tue, 24 Mar 2020 00:03:25 +0000 (01:03 +0100)
committerYounies Mahmoud <younies.mahmoud@gmail.com>
Tue, 24 Mar 2020 00:03:25 +0000 (01:03 +0100)
icu4c/source/i18n/Makefile.in
icu4c/source/i18n/getunitsdata.cpp [new file with mode: 0644]
icu4c/source/i18n/getunitsdata.h [new file with mode: 0644]
icu4c/source/i18n/i18n.vcxproj
icu4c/source/i18n/i18n.vcxproj.filters
icu4c/source/i18n/i18n_uwp.vcxproj
icu4c/source/i18n/unitsrouter.cpp
icu4c/source/i18n/unitsrouter.h
icu4c/source/test/intltest/unitstest.cpp

index 3b8e99ac895c767efbe0c1f25505e80ef96a9326..013f5ba18912c352f77cedec011dd139e6191e00 100644 (file)
@@ -115,7 +115,7 @@ numparse_affixes.o numparse_compositions.o numparse_validators.o \
 numrange_fluent.o numrange_impl.o \
 erarules.o \
 formattedvalue.o formattedval_iterimpl.o formattedval_sbimpl.o formatted_string_builder.o \
-unitconverter.o unitsrouter.o complexunitsconverter.o
+unitconverter.o unitsrouter.o complexunitsconverter.o getunitsdata.o
 
 ## Header files to install
 HEADERS = $(srcdir)/unicode/*.h
diff --git a/icu4c/source/i18n/getunitsdata.cpp b/icu4c/source/i18n/getunitsdata.cpp
new file mode 100644 (file)
index 0000000..a53aaa3
--- /dev/null
@@ -0,0 +1,303 @@
+// © 2020 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include <utility>
+
+#include "cmemory.h"
+#include "cstring.h"
+#include "getunitsdata.h"
+#include "number_decimalquantity.h"
+#include "resource.h"
+#include "uresimp.h"
+
+U_NAMESPACE_BEGIN
+
+namespace {
+
+using icu::number::impl::DecimalQuantity;
+
+/**
+ * A ResourceSink that collects conversion rate information.
+ *
+ * Calls to put() collects ConversionRateInfo instances for  into the vector
+ * passed to the constructor, but only if an identical instance isn't already
+ * present.
+ *
+ * This class is for use by ures_getAllItemsWithFallback. Example code for
+ * collecting conversion info for "mile" and "foot" into conversionInfoOutput:
+ *
+ *     UErrorCode status = U_ZERO_ERROR;
+ *     MaybeStackVector<ConversionRateInfo> conversionInfoOutput;
+ *     ConversionRateDataSink convertSink(conversionInfoOutput);
+ *     ures_getByKey(unitsBundle, "convertUnits", &fillIn, &status);
+ *     ures_getAllItemsWithFallback(fillIn, "mile", convertSink, status);
+ *     ures_getAllItemsWithFallback(fillIn, "foot", convertSink, status);
+ */
+class ConversionRateDataSink : public ResourceSink {
+  public:
+    explicit ConversionRateDataSink(MaybeStackVector<ConversionRateInfo> &out) : outVector(out) {}
+
+    /**
+     * Adds the conversion rate information found in value to the output vector.
+     * @param key The source unit identifier.
+     * @param value A resource containing conversion rate info (a target and
+     * factor, and possibly an offset).
+     * @param noFallback Ignored.
+     * @param status The standard ICU error code output parameter.
+     */
+    void put(const char *source, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
+        ResourceTable conversionRateTable = value.getTable(status);
+        if (U_FAILURE(status)) return;
+
+        // Collect target, factor and offset from the resource.
+        int32_t lenSource = uprv_strlen(source);
+        const UChar *target = NULL, *factor = NULL, *offset = NULL;
+        int32_t lenTarget, lenFactor, lenOffset;
+        const char *key;
+        for (int32_t i = 0; conversionRateTable.getKeyAndValue(i, key, value); ++i) {
+            if (uprv_strcmp(key, "target") == 0) {
+                target = value.getString(lenTarget, status);
+            } else if (uprv_strcmp(key, "factor") == 0) {
+                factor = value.getString(lenFactor, status);
+            } else if (uprv_strcmp(key, "offset") == 0) {
+                offset = value.getString(lenOffset, status);
+            }
+        }
+        if (target == NULL || factor == NULL) {
+            status = U_MISSING_RESOURCE_ERROR;
+            return;
+        }
+
+        // Check if we already have the conversion rate in question.
+        //
+        // TODO(revieW): We could do this skip-check *before* we fetch
+        // target/factor/offset based only on the source unit, but only if we're
+        // certain we'll never get two different targets for a given source.
+        // This should be the case, since convertUnit entries in CLDR's
+        // units.xml should all point at a defined base unit for the unit
+        // category. I should make this code more efficient after
+        // double-checking we're fine with relying on such a detail from the
+        // CLDR spec?
+        CharString tmpTarget;
+        tmpTarget.appendInvariantChars(target, lenTarget, status);
+        if (U_FAILURE(status)) return;
+        for (int32_t i = 0, len = outVector.length(); i < len; i++) {
+            if (strcmp(outVector[i]->source.data(), source) == 0 &&
+                strcmp(outVector[i]->target.data(), tmpTarget.data()) == 0) {
+                return;
+            }
+        }
+        if (U_FAILURE(status)) return;
+
+        // We don't have this ConversionRateInfo yet: add it.
+        ConversionRateInfo *cr = outVector.emplaceBack();
+        if (!cr) {
+            status = U_MEMORY_ALLOCATION_ERROR;
+            return;
+        } else {
+            cr->source.append(source, lenSource, status);
+            cr->target.append(tmpTarget.data(), tmpTarget.length(), status);
+            cr->factor.appendInvariantChars(factor, lenFactor, status);
+            if (offset != NULL) cr->offset.appendInvariantChars(offset, lenOffset, status);
+        }
+    }
+
+  private:
+    MaybeStackVector<ConversionRateInfo> &outVector;
+};
+
+// WIP/FIXME: this class is currently unused code (dead?) - putUnitPref() has
+// all the features we need whereas this doesn't handle fallback to
+// usage="default" and region="001" yet. If we want to fix that here, this code
+// will get quite a bit more complicated.
+class UnitPreferencesSink : public ResourceSink {
+  public:
+    explicit UnitPreferencesSink(MaybeStackVector<UnitPreference> &out) : outVector(out) {}
+
+    void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
+        if (U_FAILURE(status)) { return; }
+        int32_t prefLen;
+        ResourceArray unitPrefs = value.getArray(status);
+        if (U_FAILURE(status)) { return; }
+        prefLen = unitPrefs.getSize();
+        for (int32_t i = 0; unitPrefs.getValue(i, value); i++) {
+            UnitPreference *up = outVector.emplaceBack();
+            if (!up) {
+                status = U_MEMORY_ALLOCATION_ERROR;
+                return;
+            }
+            ResourceTable unitPref = value.getTable(status);
+            if (U_FAILURE(status)) { return; }
+            for (int32_t i = 0; unitPref.getKeyAndValue(i, key, value); ++i) {
+                if (uprv_strcmp(key, "unit") == 0) {
+                    int32_t length;
+                    const UChar *u = value.getString(length, status);
+                    up->unit.appendInvariantChars(u, length, status);
+                } else if (uprv_strcmp(key, "geq") == 0) {
+                    int32_t length;
+                    const UChar *g = value.getString(length, status);
+                    CharString geq;
+                    geq.appendInvariantChars(g, length, status);
+                    DecimalQuantity dq;
+                    dq.setToDecNumber(geq.data(), status);
+                    up->geq = dq.toDouble();
+                } else if (uprv_strcmp(key, "skeleton") == 0) {
+                    int32_t length;
+                    const UChar *s = value.getString(length, status);
+                    up->skeleton.appendInvariantChars(s, length, status);
+                }
+            }
+        }
+    }
+
+  private:
+    MaybeStackVector<UnitPreference> &outVector;
+};
+
+/**
+ * Collects unit preference information from a set of preferences.
+ * @param usageData This should be a resource bundle containing a vector of
+ * preferences - i.e. the unitPreferenceData tree resources already narrowed
+ * down to a particular usage and region (example:
+ * "unitPreferenceData/length/road/GB").
+ */
+void putUnitPref(UResourceBundle *usageData, MaybeStackVector<UnitPreference> &outVector,
+                 UErrorCode &status) {
+    if (U_FAILURE(status)) return;
+    StackUResourceBundle prefBundle;
+
+    int32_t numPrefs = ures_getSize(usageData);
+    for (int32_t i = 0; i < numPrefs; i++) {
+        ures_getByIndex(usageData, i, prefBundle.getAlias(), &status);
+
+        // Add and populate a new UnitPreference
+        int32_t strLen;
+
+        // unit
+        const UChar *unitIdent = ures_getStringByKey(prefBundle.getAlias(), "unit", &strLen, &status);
+        if (U_FAILURE(status)) return;
+        UnitPreference *up = outVector.emplaceBack();
+        if (!up) {
+            status = U_MEMORY_ALLOCATION_ERROR;
+            return;
+        }
+        up->unit.appendInvariantChars(unitIdent, strLen, status);
+
+        // geq
+        const UChar *geq = ures_getStringByKey(prefBundle.getAlias(), "geq", &strLen, &status);
+        if (U_SUCCESS(status)) {
+            // If we don't mind up->geq having a bad value when
+            // U_FAILURE(status), we could extract a function and do a one-liner:
+            // up->geq = UCharsToDouble(geq, status);
+            CharString cGeq;
+            cGeq.appendInvariantChars(geq, strLen, status);
+            DecimalQuantity dq;
+            dq.setToDecNumber(StringPiece(cGeq.data()), status);
+            if (U_FAILURE(status)) return;
+            up->geq = dq.toDouble();
+        } else if (status == U_MISSING_RESOURCE_ERROR) {
+            // We don't mind if geq is missing
+            status = U_ZERO_ERROR;
+        } else {
+            return;
+        }
+
+        // skeleton
+        const UChar *skel = ures_getStringByKey(prefBundle.getAlias(), "skeleton", &strLen, &status);
+        if (U_SUCCESS(status)) {
+            up->skeleton.appendInvariantChars(skel, strLen, status);
+        } else if (status == U_MISSING_RESOURCE_ERROR) {
+            // We don't mind if geq is missing
+            status = U_ZERO_ERROR;
+        } else {
+            return;
+        }
+    }
+}
+
+} // namespace
+
+/**
+ * Fetches the units data that would be needed for the given usage.
+ *
+ * @param inputUnit the unit for which input is expected. (NOTE/WIP: If this is
+ * known to be a base unit already, we could strip some logic here.)
+ */
+void getUnitsData(const char *outputRegion, const char *usage, const MeasureUnit &inputUnit,
+                  CharString &category, MeasureUnit &baseUnit,
+                  MaybeStackVector<ConversionRateInfo> &conversionRates,
+                  MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status) {
+    // This first fetches conversion info for the inputUnit, to find out the
+    // base unit. Next it fetches the category and unit preferences for the
+    // given usage and region. Finally it fetches conversion rates again, for
+    // each of the units in the regional preferences for the given usage.
+
+    // In this function we use LocalUResourceBundlePointers for resource bundles
+    // that don't change, and StackUResourceBundles for structures we use as
+    // fillin.
+    LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", &status));
+    StackUResourceBundle convertUnitsBundle;
+    ConversionRateDataSink convertSink(conversionRates);
+
+    // baseUnit
+    MeasureUnit inputBase = inputUnit.withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
+    ures_getByKey(unitsBundle.getAlias(), "convertUnits", convertUnitsBundle.getAlias(), &status);
+    ures_getAllItemsWithFallback(convertUnitsBundle.getAlias(), inputBase.getIdentifier(), convertSink,
+                                 status);
+    if (U_FAILURE(status)) return;
+    if (conversionRates.length() < 1) {
+        // This is defensive programming, because this shouldn't happen: if
+        // convertSink succeeds, there should be at least one item in
+        // conversionRates.
+        status = U_MISSING_RESOURCE_ERROR;
+        return;
+    }
+    const char *baseIdentifier = conversionRates[0]->target.data();
+    baseUnit = MeasureUnit::forIdentifier(baseIdentifier, status);
+
+    // category
+    LocalUResourceBundlePointer unitQuantities(
+        ures_getByKey(unitsBundle.getAlias(), "unitQuantities", NULL, &status));
+    int32_t categoryLength;
+    const UChar *uCategory =
+        ures_getStringByKey(unitQuantities.getAlias(), baseIdentifier, &categoryLength, &status);
+    category.appendInvariantChars(uCategory, categoryLength, status);
+
+    // Find the right unit preference bundle
+    StackUResourceBundle stackBundle; // Reused as we climb the tree
+    ures_getByKey(unitsBundle.getAlias(), "unitPreferenceData", stackBundle.getAlias(), &status);
+    ures_getByKey(stackBundle.getAlias(), category.data(), stackBundle.getAlias(), &status);
+    if (U_FAILURE(status)) { return; }
+    ures_getByKey(stackBundle.getAlias(), usage, stackBundle.getAlias(), &status);
+    if (status == U_MISSING_RESOURCE_ERROR) {
+        // Requested usage does not exist, so we use "default".
+        status = U_ZERO_ERROR;
+        ures_getByKey(stackBundle.getAlias(), "default", stackBundle.getAlias(), &status);
+    }
+    ures_getByKey(stackBundle.getAlias(), outputRegion, stackBundle.getAlias(), &status);
+    if (status == U_MISSING_RESOURCE_ERROR) {
+        // Requested region does not exist, so we use "001".
+        status = U_ZERO_ERROR;
+        ures_getByKey(stackBundle.getAlias(), "001", stackBundle.getAlias(), &status);
+    }
+
+    // Collect all the preferences into unitPreferences
+    putUnitPref(stackBundle.getAlias(), unitPreferences, status);
+
+    // Load ConversionRateInfo for each of the units in unitPreferences
+    for (int32_t i = 0; i < unitPreferences.length(); i++) {
+        MeasureUnit prefUnitBase = MeasureUnit::forIdentifier(unitPreferences[i]->unit.data(), status)
+                                       .withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
+        // convertSink will skip conversion rates we already have
+        ures_getAllItemsWithFallback(convertUnitsBundle.getAlias(), prefUnitBase.getIdentifier(),
+                                     convertSink, status);
+    }
+}
+
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
\ No newline at end of file
diff --git a/icu4c/source/i18n/getunitsdata.h b/icu4c/source/i18n/getunitsdata.h
new file mode 100644 (file)
index 0000000..da7ab68
--- /dev/null
@@ -0,0 +1,44 @@
+// © 2020 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+#ifndef __GETUNITSDATA_H__
+#define __GETUNITSDATA_H__
+
+#include "charstr.h" // CharString
+#include "cmemory.h"
+#include "complexunitsconverter.h"
+#include "unicode/errorcode.h"
+#include "unicode/measunit.h"
+#include "unicode/measure.h"
+#include "unicode/stringpiece.h"
+
+U_NAMESPACE_BEGIN
+
+struct ConversionRateInfo {
+    CharString source;
+    CharString target;
+    CharString factor;
+    CharString offset;
+};
+
+struct UnitPreference {
+    UnitPreference() : geq(1) {}
+    CharString unit;
+    double geq;
+    CharString skeleton;
+};
+
+// TODO(hugo): Add a comment.
+void U_I18N_API getUnitsData(const char *outputRegion, const char *usage, const MeasureUnit &inputUnit,
+                             CharString &category, MeasureUnit &baseUnit,
+                             MaybeStackVector<ConversionRateInfo> &conversionInfo,
+                             MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status);
+
+U_NAMESPACE_END
+
+#endif //__GETUNITSDATA_H__
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
index 685946ab9439b12badbc1095e0494fa0de1470c9..c124d8fce457344b9e9abfd7cdc01424b51dc229 100644 (file)
     <ClCompile Include="unitconverter.cpp" />
     <ClCompile Include="complexunitsconverter.cpp" />
     <ClCompile Include="unitsrouter.cpp" />
+    <ClCompile Include="getunitsdata.cpp" />
     <ClCompile Include="unum.cpp" />
     <ClCompile Include="unumsys.cpp" />
     <ClCompile Include="upluralrules.cpp" />
     <ClInclude Include="unitconverter.h" />
     <ClInclude Include="complexunitsconverter.h" />
     <ClInclude Include="unitsrouter.h" />
+    <ClInclude Include="getunitsdata.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="i18n.rc" />
index 9ed1d9bb6c53526de19998ae9b75013a4c4bb28a..c1ce5b76c5a561223d40c4ac96e26b51f334c0a4 100644 (file)
     <ClCompile Include="unitsrouter.cpp">
       <Filter>formatting</Filter>
     </ClCompile>
+    <ClCompile Include="getunitsdata.cpp">
+      <Filter>formatting</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="bocsu.cpp">
     <ClInclude Include="unitsrouter.h">
       <Filter>formatting</Filter>
     </ClInclude>
+    <ClInclude Include="getunitsdata.h">
+      <Filter>formatting</Filter>
+    </ClInclude>
     <ClInclude Include="vzone.h">
       <Filter>formatting</Filter>
     </ClInclude>
index a350f4090f4d6127590be9bb67cd9c80bf91fa90..c8bdb9309a4b4cce3f903ee6f02a0bdfec863a12 100644 (file)
     <ClCompile Include="unitconverter.cpp" />
     <ClCompile Include="complexunitsconverter.cpp" />
     <ClCompile Include="unitsrouter.cpp" />
+    <ClCompile Include="getunitsdata.cpp" />
     <ClCompile Include="unum.cpp" />
     <ClCompile Include="unumsys.cpp" />
     <ClCompile Include="upluralrules.cpp" />
     <ClInclude Include="unitconverter.h" />
     <ClInclude Include="complexunitsconverter.h" />
     <ClInclude Include="unitsrouter.h" />
+    <ClInclude Include="getunitsdata.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="i18n.rc" />
index aa0005ea6da6fe91e3462190a9cd71c2b12d4b1e..f727cfa1522381e26f6bae32406062c7874ff3b7 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "cmemory.h"
 #include "cstring.h"
+#include "getunitsdata.h"
 #include "number_decimalquantity.h"
 #include "resource.h"
 #include "unitsrouter.h"
 
 U_NAMESPACE_BEGIN
 
-namespace {
-/* Internal Data */
-// // Preference of a single unit.
-// struct UnitPreference {
-//     StringPiece identifier;
-
-//     // Represents the limit of the largest unit in the identifier that the quantity must be greater than
-//     // or equal.
-//     // e.g. geq: 0.3 for a unit "foot-and-inch"
-//     double limit;
-// };
-
-// MaybeStackVector<UnitPreference> extractUnitPreferences(StringPiece locale, StringPiece usage,
-//                                                         StringPiece category) {
-//     MaybeStackVector<UnitPreference> result;
-
-//     // TODO(hugovdm): extract from the database all the UnitPreference for the `locale`, `category` and
-//     // `usage` in order.
-
-//     return result;
-// }
-
-// StringPiece extractUnitCategory(MeasureUnit unit) {
-//     StringPiece result;
-
-//     // TODO(hugovdm): extract the category of a unit from their MeasureUnits.
-
-//     return result;
-// }
-
-using icu::number::impl::DecimalQuantity;
-
-/**
- * A ResourceSink that collects conversion rate information.
- *
- * Calls to put() collects ConversionRateInfo instances for  into the vector
- * passed to the constructor, but only if an identical instance isn't already
- * present.
- *
- * This class is for use by ures_getAllItemsWithFallback. Example code for
- * collecting conversion info for "mile" and "foot" into conversionInfoOutput:
- *
- *     UErrorCode status = U_ZERO_ERROR;
- *     MaybeStackVector<ConversionRateInfo> conversionInfoOutput;
- *     ConversionRateDataSink convertSink(conversionInfoOutput);
- *     ures_getByKey(unitsBundle, "convertUnits", &fillIn, &status);
- *     ures_getAllItemsWithFallback(fillIn, "mile", convertSink, status);
- *     ures_getAllItemsWithFallback(fillIn, "foot", convertSink, status);
- */
-class ConversionRateDataSink : public ResourceSink {
-  public:
-    explicit ConversionRateDataSink(MaybeStackVector<ConversionRateInfo> &out) : outVector(out) {}
-
-    /**
-     * Adds the conversion rate information found in value to the output vector.
-     * @param key The source unit identifier.
-     * @param value A resource containing conversion rate info (a target and
-     * factor, and possibly an offset).
-     * @param noFallback Ignored.
-     * @param status The standard ICU error code output parameter.
-     */
-    void put(const char *source, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
-        ResourceTable conversionRateTable = value.getTable(status);
-        if (U_FAILURE(status)) return;
-
-        // Collect target, factor and offset from the resource.
-        int32_t lenSource = uprv_strlen(source);
-        const UChar *target = NULL, *factor = NULL, *offset = NULL;
-        int32_t lenTarget, lenFactor, lenOffset;
-        const char *key;
-        for (int32_t i = 0; conversionRateTable.getKeyAndValue(i, key, value); ++i) {
-            if (uprv_strcmp(key, "target") == 0) {
-                target = value.getString(lenTarget, status);
-            } else if (uprv_strcmp(key, "factor") == 0) {
-                factor = value.getString(lenFactor, status);
-            } else if (uprv_strcmp(key, "offset") == 0) {
-                offset = value.getString(lenOffset, status);
-            }
-        }
-        if (target == NULL || factor == NULL) {
-            status = U_MISSING_RESOURCE_ERROR;
-            return;
-        }
-
-        // Check if we already have the conversion rate in question.
-        //
-        // TODO(revieW): We could do this skip-check *before* we fetch
-        // target/factor/offset based only on the source unit, but only if we're
-        // certain we'll never get two different targets for a given source.
-        // This should be the case, since convertUnit entries in CLDR's
-        // units.xml should all point at a defined base unit for the unit
-        // category. I should make this code more efficient after
-        // double-checking we're fine with relying on such a detail from the
-        // CLDR spec?
-        CharString tmpTarget;
-        tmpTarget.appendInvariantChars(target, lenTarget, status);
-        if (U_FAILURE(status)) return;
-        for (int32_t i = 0, len = outVector.length(); i < len; i++) {
-            if (strcmp(outVector[i]->source.data(), source) == 0 &&
-                strcmp(outVector[i]->target.data(), tmpTarget.data()) == 0) {
-                return;
-            }
-        }
-        if (U_FAILURE(status)) return;
-
-        // We don't have this ConversionRateInfo yet: add it.
-        ConversionRateInfo *cr = outVector.emplaceBack();
-        if (!cr) {
-            status = U_MEMORY_ALLOCATION_ERROR;
-            return;
-        } else {
-            cr->source.append(source, lenSource, status);
-            cr->target.append(tmpTarget.data(), tmpTarget.length(), status);
-            cr->factor.appendInvariantChars(factor, lenFactor, status);
-            if (offset != NULL) cr->offset.appendInvariantChars(offset, lenOffset, status);
-        }
-    }
-
-  private:
-    MaybeStackVector<ConversionRateInfo> &outVector;
-};
-
-// WIP/FIXME: this class is currently unused code (dead?) - putUnitPref() has
-// all the features we need whereas this doesn't handle fallback to
-// usage="default" and region="001" yet. If we want to fix that here, this code
-// will get quite a bit more complicated.
-class UnitPreferencesSink : public ResourceSink {
-  public:
-    explicit UnitPreferencesSink(MaybeStackVector<UnitPreference> &out) : outVector(out) {}
-
-    void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
-        if (U_FAILURE(status)) { return; }
-        int32_t prefLen;
-        ResourceArray unitPrefs = value.getArray(status);
-        if (U_FAILURE(status)) { return; }
-        prefLen = unitPrefs.getSize();
-        for (int32_t i = 0; unitPrefs.getValue(i, value); i++) {
-            UnitPreference *up = outVector.emplaceBack();
-            if (!up) {
-                status = U_MEMORY_ALLOCATION_ERROR;
-                return;
-            }
-            ResourceTable unitPref = value.getTable(status);
-            if (U_FAILURE(status)) { return; }
-            for (int32_t i = 0; unitPref.getKeyAndValue(i, key, value); ++i) {
-                if (uprv_strcmp(key, "unit") == 0) {
-                    int32_t length;
-                    const UChar *u = value.getString(length, status);
-                    up->unit.appendInvariantChars(u, length, status);
-                } else if (uprv_strcmp(key, "geq") == 0) {
-                    int32_t length;
-                    const UChar *g = value.getString(length, status);
-                    CharString geq;
-                    geq.appendInvariantChars(g, length, status);
-                    DecimalQuantity dq;
-                    dq.setToDecNumber(geq.data(), status);
-                    up->geq = dq.toDouble();
-                } else if (uprv_strcmp(key, "skeleton") == 0) {
-                    int32_t length;
-                    const UChar *s = value.getString(length, status);
-                    up->skeleton.appendInvariantChars(s, length, status);
-                }
-            }
-        }
-    }
-
-  private:
-    MaybeStackVector<UnitPreference> &outVector;
-};
-
-/**
- * Collects unit preference information from a set of preferences.
- * @param usageData This should be a resource bundle containing a vector of
- * preferences - i.e. the unitPreferenceData tree resources already narrowed
- * down to a particular usage and region (example:
- * "unitPreferenceData/length/road/GB").
- */
-void putUnitPref(UResourceBundle *usageData, MaybeStackVector<UnitPreference> &outVector,
-                 UErrorCode &status) {
-    if (U_FAILURE(status)) return;
-    StackUResourceBundle prefBundle;
-
-    int32_t numPrefs = ures_getSize(usageData);
-    for (int32_t i = 0; i < numPrefs; i++) {
-        ures_getByIndex(usageData, i, prefBundle.getAlias(), &status);
-
-        // Add and populate a new UnitPreference
-        int32_t strLen;
-
-        // unit
-        const UChar *unitIdent = ures_getStringByKey(prefBundle.getAlias(), "unit", &strLen, &status);
-        if (U_FAILURE(status)) return;
-        UnitPreference *up = outVector.emplaceBack();
-        if (!up) {
-            status = U_MEMORY_ALLOCATION_ERROR;
-            return;
-        }
-        up->unit.appendInvariantChars(unitIdent, strLen, status);
-
-        // geq
-        const UChar *geq = ures_getStringByKey(prefBundle.getAlias(), "geq", &strLen, &status);
-        if (U_SUCCESS(status)) {
-            // If we don't mind up->geq having a bad value when
-            // U_FAILURE(status), we could extract a function and do a one-liner:
-            // up->geq = UCharsToDouble(geq, status);
-            CharString cGeq;
-            cGeq.appendInvariantChars(geq, strLen, status);
-            DecimalQuantity dq;
-            dq.setToDecNumber(StringPiece(cGeq.data()), status);
-            if (U_FAILURE(status)) return;
-            up->geq = dq.toDouble();
-        } else if (status == U_MISSING_RESOURCE_ERROR) {
-            // We don't mind if geq is missing
-            status = U_ZERO_ERROR;
-        } else {
-            return;
-        }
-
-        // skeleton
-        const UChar *skel = ures_getStringByKey(prefBundle.getAlias(), "skeleton", &strLen, &status);
-        if (U_SUCCESS(status)) {
-            up->skeleton.appendInvariantChars(skel, strLen, status);
-        } else if (status == U_MISSING_RESOURCE_ERROR) {
-            // We don't mind if geq is missing
-            status = U_ZERO_ERROR;
-        } else {
-            return;
-        }
-    }
-}
-
-} // namespace
-
-/**
- * Fetches the units data that would be needed for the given usage.
- *
- * @param inputUnit the unit for which input is expected. (NOTE/WIP: If this is
- * known to be a base unit already, we could strip some logic here.)
- */
-void getUnitsData(const char *outputRegion, const char *usage, const MeasureUnit &inputUnit,
-                  CharString &category, MeasureUnit &baseUnit,
-                  MaybeStackVector<ConversionRateInfo> &conversionRates,
-                  MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status) {
-    // This first fetches conversion info for the inputUnit, to find out the
-    // base unit. Next it fetches the category and unit preferences for the
-    // given usage and region. Finally it fetches conversion rates again, for
-    // each of the units in the regional preferences for the given usage.
-
-    // In this function we use LocalUResourceBundlePointers for resource bundles
-    // that don't change, and StackUResourceBundles for structures we use as
-    // fillin.
-    LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", &status));
-    StackUResourceBundle convertUnitsBundle;
-    ConversionRateDataSink convertSink(conversionRates);
-
-    // baseUnit
-    MeasureUnit inputBase = inputUnit.withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
-    ures_getByKey(unitsBundle.getAlias(), "convertUnits", convertUnitsBundle.getAlias(), &status);
-    ures_getAllItemsWithFallback(convertUnitsBundle.getAlias(), inputBase.getIdentifier(), convertSink,
-                                 status);
-    if (U_FAILURE(status)) return;
-    if (conversionRates.length() < 1) {
-        // This is defensive programming, because this shouldn't happen: if
-        // convertSink succeeds, there should be at least one item in
-        // conversionRates.
-        status = U_MISSING_RESOURCE_ERROR;
-        return;
-    }
-    const char *baseIdentifier = conversionRates[0]->target.data();
-    baseUnit = MeasureUnit::forIdentifier(baseIdentifier, status);
-
-    // category
-    LocalUResourceBundlePointer unitQuantities(
-        ures_getByKey(unitsBundle.getAlias(), "unitQuantities", NULL, &status));
-    int32_t categoryLength;
-    const UChar *uCategory =
-        ures_getStringByKey(unitQuantities.getAlias(), baseIdentifier, &categoryLength, &status);
-    category.appendInvariantChars(uCategory, categoryLength, status);
-
-    // Find the right unit preference bundle
-    StackUResourceBundle stackBundle; // Reused as we climb the tree
-    ures_getByKey(unitsBundle.getAlias(), "unitPreferenceData", stackBundle.getAlias(), &status);
-    ures_getByKey(stackBundle.getAlias(), category.data(), stackBundle.getAlias(), &status);
-    if (U_FAILURE(status)) { return; }
-    ures_getByKey(stackBundle.getAlias(), usage, stackBundle.getAlias(), &status);
-    if (status == U_MISSING_RESOURCE_ERROR) {
-        // Requested usage does not exist, so we use "default".
-        status = U_ZERO_ERROR;
-        ures_getByKey(stackBundle.getAlias(), "default", stackBundle.getAlias(), &status);
-    }
-    ures_getByKey(stackBundle.getAlias(), outputRegion, stackBundle.getAlias(), &status);
-    if (status == U_MISSING_RESOURCE_ERROR) {
-        // Requested region does not exist, so we use "001".
-        status = U_ZERO_ERROR;
-        ures_getByKey(stackBundle.getAlias(), "001", stackBundle.getAlias(), &status);
-    }
-
-    // Collect all the preferences into unitPreferences
-    putUnitPref(stackBundle.getAlias(), unitPreferences, status);
-
-    // Load ConversionRateInfo for each of the units in unitPreferences
-    for (int32_t i = 0; i < unitPreferences.length(); i++) {
-        MeasureUnit prefUnitBase = MeasureUnit::forIdentifier(unitPreferences[i]->unit.data(), status)
-                                       .withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
-        // convertSink will skip conversion rates we already have
-        ures_getAllItemsWithFallback(convertUnitsBundle.getAlias(), prefUnitBase.getIdentifier(),
-                                     convertSink, status);
-    }
-}
-
 UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage,
                          UErrorCode &status) {
     // StringPiece unitCategory = extractUnitCategory(inputUnit);
-    // MaybeStackVector<UnitPreference> preferences = extractUnitPreferences(locale, usage, unitCategory);
+    // MaybeStackVector<UnitPreference> preferences = extractUnitPreferences(locale, usage,
+    // unitCategory);
     const char *region = "001"; // FIXME extract from locale.
     CharString category;
     MeasureUnit baseUnit;
index 23058b702b332b51b26c03907e589a79992b1765..0b06c86f7a6269deeaf24d2a64a846f74907e958 100644 (file)
 
 U_NAMESPACE_BEGIN
 
-using icu::CharString;
-using icu::MaybeStackVector;
-using icu::MeasureUnit;
-
-struct ConversionRateInfo {
-    CharString source;
-    CharString target;
-    CharString factor;
-    CharString offset;
-};
-
-struct UnitPreference {
-    UnitPreference() : geq(1) {}
-    CharString unit;
-    double geq;
-    CharString skeleton;
-};
-
 struct ConverterPreference {
     ComplexUnitsConverter converter;
     double limit;
@@ -53,12 +35,6 @@ class U_I18N_API UnitsRouter {
     MaybeStackVector<ConverterPreference> converterPreferences_;
 };
 
-// TODO(hugo): Add a comment.
-void U_I18N_API getUnitsData(const char *outputRegion, const char *usage, const MeasureUnit &inputUnit,
-                CharString &category, MeasureUnit &baseUnit,
-                MaybeStackVector<ConversionRateInfo> &conversionInfo,
-                MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status);
-
 U_NAMESPACE_END
 
 #endif //__UNITSROUTER_H__
index ef9bba5c8b55681f161096f48d004be9c241a6b6..b055f7ff95d85f7a51694ed51d2e6150408914d2 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "charstr.h"
 #include "filestrm.h"
+#include "getunitsdata.h"
 #include "intltest.h"
 #include "number_decimalquantity.h"
 #include "unicode/ctest.h"
@@ -508,7 +509,7 @@ void runDataDrivenConversionTest(void *context, char *fields[][2], int32_t field
             return;
         }
         double got = converter.convert(1000);
-        ((UnitsTest*)context)->assertEqualsNear(fields[0][0], expected, got, 0.0001);
+        ((UnitsTest *)context)->assertEqualsNear(fields[0][0], expected, got, 0.0001);
     }
 }