]> granicus.if.org Git - icu/commitdiff
Working parsing and returning of units data.
authorHugo van der Merwe <17109322+hugovdm@users.noreply.github.com>
Wed, 18 Mar 2020 15:47:46 +0000 (16:47 +0100)
committerHugo van der Merwe <17109322+hugovdm@users.noreply.github.com>
Wed, 18 Mar 2020 17:54:04 +0000 (18:54 +0100)
icu4c/source/test/intltest/Makefile.in
icu4c/source/test/intltest/unitstest.cpp
icu4c/source/test/intltest/wip_units_resource_loader.cpp [new file with mode: 0644]
icu4c/source/test/intltest/wip_units_resource_loader.h [new file with mode: 0644]

index ff640d523d59384bafab7f3db382f5380e8bfc7b..3b42e0c572385af336098ffecd76b44a0e61f2c2 100644 (file)
@@ -69,7 +69,7 @@ string_segment_test.o \
 numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
 static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \
 formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o \
-unitstest.o WIP_units_resources.o
+unitstest.o WIP_units_resources.o wip_units_resource_loader.o
 
 DEPS = $(OBJECTS:.o=.d)
 
index d7d3f9ac95470cc7e00ba0e55d439b8c13daf5c2..b37dad48f16f6d6f2982ff99ad4d852296d9bc9a 100644 (file)
@@ -18,6 +18,7 @@
 using icu::number::impl::DecimalQuantity;
 
 #include "wip_units_resources.h"
+#include "wip_units_resource_loader.h"
 
 class UnitsTest : public IntlTest {
   public:
@@ -34,6 +35,7 @@ class UnitsTest : public IntlTest {
     void testArea();
     void testUSVolumeResourceLoading();
     void testSIMassResourceLoading();
+    void testGetUnitsData();
 };
 
 extern IntlTest *createUnitsTest() { return new UnitsTest(); }
@@ -52,6 +54,7 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
     TESTCASE_AUTO(testArea);
     TESTCASE_AUTO(testUSVolumeResourceLoading);
     TESTCASE_AUTO(testSIMassResourceLoading);
+    TESTCASE_AUTO(testGetUnitsData);
     TESTCASE_AUTO_END;
 }
 
@@ -591,4 +594,46 @@ void UnitsTest::testSIMassResourceLoading() {
     fprintf(stderr, "baseUnit: %s\n", resources.baseUnit);
 }
 
+void UnitsTest::testGetUnitsData() {
+    struct {
+        const char *outputRegion;
+        const char *usage;
+        const char *inputUnit;
+    } testCases[]{
+        {"US", "fluid", "centiliter"},
+        {"BZ", "weather", "celsius"},
+        {"ZA", "road", "yard"},
+        {"XZ", "zz_nonexistant", "dekagram"},
+    };
+    for (const auto &t : testCases) {
+        logln("test case: %s %s %s\n", t.outputRegion, t.usage, t.inputUnit);
+        // UErrorCode status = U_ZERO_ERROR;
+        IcuTestErrorCode status(*this, "testGetUnitsData");
+        MeasureUnit inputUnit = MeasureUnit::forIdentifier(t.inputUnit, status);
+
+        CharString category;
+        MeasureUnit baseUnit;
+        MaybeStackVector<ConversionRateInfo> conversionInfo;
+        MaybeStackVector<UnitPreference> unitPreferences;
+        getUnitsData(t.outputRegion, t.usage, inputUnit, category, baseUnit, conversionInfo,
+                     unitPreferences, status);
+        if (status.errIfFailureAndReset("getUnitsData(\"%s\", \"%s\", \"%s\", ...)", t.outputRegion, t.usage, t.inputUnit)) {
+            continue;
+        }
+        logln("category: \"%s\", baseUnit: \"%s\"", category.data(), baseUnit.getIdentifier());
+        for (int i=0; i < conversionInfo.length(); i++) {
+            ConversionRateInfo *cri;
+            cri = conversionInfo[i];
+            logln("conversionInfo %d: source=\"%s\", target=\"%s\", factor=\"%s\", offset=\"%s\"", i,
+                  cri->source.data(), cri->target.data(), cri->factor.data(), cri->offset.data());
+        }
+        for (int i=0; i < unitPreferences.length(); i++) {
+            UnitPreference *up;
+            up = unitPreferences[i];
+            logln("unitPreference %d: \"%s\", geq=%f, skeleton=\"%s\"", i, up->unit.data(),
+                  up->geq, up->skeleton.data());
+        }
+    }
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/wip_units_resource_loader.cpp b/icu4c/source/test/intltest/wip_units_resource_loader.cpp
new file mode 100644 (file)
index 0000000..256c336
--- /dev/null
@@ -0,0 +1,241 @@
+#include "wip_units_resource_loader.h"
+#include "cstring.h" // uprv_strcmp
+#include "cmemory.h" // MaybeStackVector
+#include "number_decimalquantity.h" // DecimalQuantity
+#include "uresimp.h" // ures_getAllItemsWithFallback
+
+using namespace icu;
+using icu::number::impl::DecimalQuantity;
+
+namespace {
+
+// Resources. It should be migrated to a permanent location with updated API,
+// once we know what that will look like and where that will be.
+
+class ConvertUnitsSink : public ResourceSink {
+  public:
+    explicit ConvertUnitsSink(MaybeStackVector<ConversionRateInfo> &out) : outVector(out) {}
+
+    // WIP: look into noFallback
+    void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) {
+        ResourceTable conversionRateTable = value.getTable(status);
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "%s: getTable failed\n", u_errorName(status));
+            return;
+        }
+
+        ConversionRateInfo *cr = outVector.emplaceBack();
+        if (!cr) {
+            status = U_MEMORY_ALLOCATION_ERROR;
+            return;
+        }
+
+        int32_t length = uprv_strlen(key);
+        cr->source.append(key, length, status);
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "%s: source.append failed\n", u_errorName(status));
+            return;
+        }
+        for (int32_t i = 0; conversionRateTable.getKeyAndValue(i, key, value); ++i) {
+            if (uprv_strcmp(key, "factor") == 0) {
+                int32_t length;
+                const UChar *f = value.getString(length, status);
+                cr->factor.appendInvariantChars(f, length, status);
+                cr->factorUChar = f;
+            } else if (uprv_strcmp(key, "offset") == 0) {
+                int32_t length;
+                const UChar *o = value.getString(length, status);
+                cr->offset.appendInvariantChars(o, length, status);
+                cr->offsetUChar = o;
+            } else if (uprv_strcmp(key, "target") == 0) {
+                int32_t length;
+                const UChar *t = value.getString(length, status);
+                cr->target.appendInvariantChars(t, length, status);
+                cr->targetUChar = t;
+            }
+        }
+    }
+  private:
+    MaybeStackVector<ConversionRateInfo> &outVector;
+};
+
+class UnitPreferencesSink : public ResourceSink {
+  public:
+    explicit UnitPreferencesSink(MaybeStackVector<UnitPreference> &out) : outVector(out) {}
+
+    // WIP: look into noFallback
+    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;
+};
+
+void putUnitPref(UResourceBundle *usageData,
+                 MaybeStackVector<UnitPreference> &outVector, UErrorCode &status) {
+    if (U_FAILURE(status)) { return; }
+
+    UResourceBundle *prefBundle = NULL;
+    int32_t numPrefs = ures_getSize(usageData);
+    for (int32_t i = 0; i < numPrefs; i++) {
+        prefBundle = ures_getByIndex(usageData, i, prefBundle, &status);
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "failed getting index %d/%d: %s\n", i, numPrefs, u_errorName(status));
+            status = U_ZERO_ERROR;
+            break;
+        }
+        int32_t len;
+        const UChar *unitIdent = ures_getStringByKey(prefBundle, "unit", &len, &status);
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "open unit failed: %s\n", u_errorName(status));
+            break;
+        }
+
+        UnitPreference *up = outVector.emplaceBack();
+        if (!up) {
+            status = U_MEMORY_ALLOCATION_ERROR;
+            return;
+        }
+        up->unit.appendInvariantChars(unitIdent, len, status);
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "failed appending unitIdent: %s\n", u_errorName(status));
+            status = U_ZERO_ERROR;
+            break;
+        }
+        const UChar *geq = ures_getStringByKey(prefBundle, "geq", &len, &status);
+        if (U_SUCCESS(status)) {
+            CharString cGeq;
+            cGeq.appendInvariantChars(geq, len, status);
+            DecimalQuantity dq;
+            dq.setToDecNumber(StringPiece(cGeq.data()), status);
+            // fprintf(stderr, "status: %s, geq: %s, dq.toDouble(): %f\n", u_errorName(status), cGeq.data(),
+            //         dq.toDouble());
+            up->geq = dq.toDouble();
+        } else if (status == U_MISSING_RESOURCE_ERROR) {
+            status = U_ZERO_ERROR;
+        }
+        if (U_FAILURE(status)) {
+            // fprintf(stderr, "failed appending geq: %s\n", u_errorName(status));
+            break;
+        }
+        const UChar *skel = ures_getStringByKey(prefBundle, "skeleton", &len, &status);
+        if (U_SUCCESS(status)) {
+            up->skeleton.appendInvariantChars(skel, len, status);
+        } else if (status == U_MISSING_RESOURCE_ERROR) {
+            status = U_ZERO_ERROR;
+        }
+    }
+    ures_close(prefBundle);
+}
+
+} // namespace
+
+/**
+ * Fetches required data FIXME.
+ *
+ * @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> &conversionInfo,
+                  MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status) {
+    // One can also use a StackUResourceBundle as a fill-in.
+    LocalUResourceBundlePointer unitsBundle(ures_openDirect(NULL, "units", &status));
+    if (U_FAILURE(status)) {
+        // fprintf(stderr, "%s: ures_openDirect %s\n", u_errorName(status), "units");
+        return;
+    }
+
+    MeasureUnit inputBase = inputUnit.withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
+    if (uprv_strcmp(inputBase.getIdentifier(), "gram") == 0) { inputBase = MeasureUnit::getKilogram(); }
+    // if (U_FAILURE(status)) fprintf(stderr, "failed getting inputBase: %s\n", u_errorName(status));
+
+    StackUResourceBundle stackBundle;
+    // CharString has initial capacity 40. Key appending only gets slow when we
+    // go beyond. TODO(hugovdm): look at how often this might happen though?
+    // Each append could be a re-allocation.
+    CharString key;
+    // key.append("convertUnits/", status);
+    key.append(inputBase.getIdentifier(), status);
+    ConvertUnitsSink convertSink(conversionInfo);
+    ures_getByKey(unitsBundle.getAlias(), "convertUnits", stackBundle.getAlias(), &status);
+    ures_getAllItemsWithFallback(stackBundle.getAlias(), key.data(), convertSink, status);
+    const CharString &baseIdentifier = conversionInfo[0]->target;
+    baseUnit = MeasureUnit::forIdentifier(baseIdentifier.data(), status);
+
+    // key.clear();
+    // key.append("unitQuantities/", status);
+    // key.append(baseIdentifier, status);
+    // ures_findSubResource(unitsBundle.getAlias(), key.data(), fillIn, &status);
+    // Now we still need to convert to string.
+    LocalUResourceBundlePointer unitQuantities(
+        ures_getByKey(unitsBundle.getAlias(), "unitQuantities", NULL, &status));
+    int32_t categoryLength;
+    const UChar *uCategory =
+        ures_getStringByKey(unitQuantities.getAlias(), baseIdentifier.data(), &categoryLength, &status);
+    category.appendInvariantChars(uCategory, categoryLength, status);
+
+    // We load the region-specific unit preferences into stackBundle, reusing it
+    // for fill-in every step of the way:
+    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, use "default".
+        status = U_ZERO_ERROR;
+        ures_getByKey(stackBundle.getAlias(), "default", stackBundle.getAlias(), &status);
+    }
+    // if (U_FAILURE(status)) fprintf(stderr, "failed getting usage %s: %s\n", usage, u_errorName(status));
+    ures_getByKey(stackBundle.getAlias(), outputRegion, stackBundle.getAlias(), &status);
+    if (status == U_MISSING_RESOURCE_ERROR) {
+        // Requested region does not exist, use "001".
+        status = U_ZERO_ERROR;
+        ures_getByKey(stackBundle.getAlias(), "001", stackBundle.getAlias(), &status);
+    }
+    // if (U_FAILURE(status)) fprintf(stderr, "failed getting region %s: %s\n", outputRegion, u_errorName(status));
+    putUnitPref(stackBundle.getAlias(), unitPreferences, status);
+    // if (U_FAILURE(status)) fprintf(stderr, "putUnitPref failed: %s\n", u_errorName(status));
+
+    // An alterantive for the above "We load ..." block, I don't think this is neater:
+    // key.clear();
+    // key.append("unitPreferenceData/", status);
+    // key.append(category, status).append("/", status);
+    // key.append(usage, status).append("/", status); // FIXME: fall back to "default"
+    // key.append(outputRegion, status); // FIXME: fall back to "001"
+    // UnitPreferencesSink prefsSink(unitPreferences);
+    // ures_getAllItemsWithFallback(unitsBundle.getAlias(), key.data(), prefsSink, status);
+}
diff --git a/icu4c/source/test/intltest/wip_units_resource_loader.h b/icu4c/source/test/intltest/wip_units_resource_loader.h
new file mode 100644 (file)
index 0000000..36f923c
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef WIP_UNITS_RESOURCE_LOADER_H
+#define WIP_UNITS_RESOURCE_LOADER_H
+
+#include "charstr.h" // CharString
+#include "unicode/measunit.h" // MeasureUnit
+
+using icu::CharString;
+using icu::MaybeStackVector;
+using icu::MeasureUnit;
+
+struct ConversionRateInfo {
+    CharString source;
+    CharString target;
+    CharString factor;
+    CharString offset;
+
+    const UChar *factorUChar;
+    const UChar *offsetUChar;
+    // WIP: This is a UChar* so that it can point at the resource. We could
+    // convert it to a CharString and own it ourselves, or if we can trust
+    // another owner's lifetime management we can make it a char*.
+    const UChar *targetUChar;
+
+    bool reciprocal = false;
+};
+
+struct UnitPreference {
+    UnitPreference() : geq(0) {}
+    CharString unit;
+    double geq;
+    CharString skeleton;
+};
+
+void getUnitsData(const char *outputRegion, const char *usage, const MeasureUnit &inputUnit,
+                  CharString &category, MeasureUnit &baseUnit,
+                  MaybeStackVector<ConversionRateInfo> &conversionInfo,
+                  MaybeStackVector<UnitPreference> &unitPreferences, UErrorCode &status);
+
+#endif // WIP_UNITS_RESOURCE_LOADER_H