diff --git a/icu4c/source/i18n/fieldposutil.h b/icu4c/source/i18n/fieldposutil.h new file mode 100644 index 00000000000..8a2053640c2 --- /dev/null +++ b/icu4c/source/i18n/fieldposutil.h @@ -0,0 +1,50 @@ +// © 2018 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 __SOURCE_FIELDPOSUTIL_H__ +#define __SOURCE_FIELDPOSUTIL_H__ + +U_NAMESPACE_BEGIN + +/** + * Wraps a UFieldPosition and makes it usable as a FieldPosition. Example: + * + *
+ * UFieldPositionWrapper wrapper(myUFPos);
+ * u_favorite_function_taking_ufpos(wrapper);
+ * // when destructed, the wrapper saves the data back into myUFPos
+ * 
+ */ +class UFieldPositionWrapper : public UMemory { + public: + explicit UFieldPositionWrapper(UFieldPosition& ufpos) + : _ufpos(ufpos) { + _fpos.setField(_ufpos.field); + _fpos.setBeginIndex(_ufpos.beginIndex); + _fpos.setEndIndex(_ufpos.endIndex); + } + + /** When destructed, copies the information from the fpos into the ufpos. */ + ~UFieldPositionWrapper() { + _ufpos.field = _fpos.getField(); + _ufpos.beginIndex = _fpos.getBeginIndex(); + _ufpos.endIndex = _fpos.getEndIndex(); + } + + /** Conversion operator to FieldPosition */ + operator FieldPosition&() { + return _fpos; + } + + private: + FieldPosition _fpos; + UFieldPosition& _ufpos; +}; + +U_NAMESPACE_END + +#endif //__SOURCE_FIELDPOSUTIL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp index bb2d47b48f6..1dc2955faa9 100644 --- a/icu4c/source/i18n/number_capi.cpp +++ b/icu4c/source/i18n/number_capi.cpp @@ -13,6 +13,7 @@ #include "number_utypes.h" #include "unicode/numberformatter.h" #include "unicode/unumberformatter.h" +#include "fieldposutil.h" using namespace icu; using namespace icu::number; @@ -153,16 +154,47 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf } U_CAPI void U_EXPORT2 -unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec) { - const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, *ec); +unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec) { + if (ufpos == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); if (U_FAILURE(*ec)) { return; } + + UFieldPositionWrapper helper(*ufpos); + result->string.populateFieldPosition(helper, 0, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec) { + if (ufpositer == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + auto* helper = reinterpret_cast(ufpositer); + result->string.populateFieldPositionIterator(*helper, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult) { + UErrorCode localStatus = U_ZERO_ERROR; + const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, localStatus); + if (U_FAILURE(localStatus)) { return; } delete impl; } U_CAPI void U_EXPORT2 -unumf_close(UNumberFormatter* f, UErrorCode* ec) { - const UNumberFormatterData* impl = UNumberFormatterData::validate(f, *ec); - if (U_FAILURE(*ec)) { return; } +unumf_close(UNumberFormatter* f) { + UErrorCode localStatus = U_ZERO_ERROR; + const UNumberFormatterData* impl = UNumberFormatterData::validate(f, localStatus); + if (U_FAILURE(localStatus)) { return; } delete impl; } diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index ce46fb34dab..059cbb64916 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -7,6 +7,9 @@ #ifndef __UNUMBERFORMATTER_H__ #define __UNUMBERFORMATTER_H__ +#include "unicode/ufieldpositer.h" +#include "unicode/umisc.h" + /** * \file @@ -441,25 +444,62 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf /** - * Releases the UFormattedNumber returned by unumf_formatDouble and friends. + * Determines the start and end indices of the first occurrence of the given field in the output string. + * This allows you to determine the locations of the integer part, fraction part, and sign. * - * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * If a field occurs multiple times in an output string, such as a grouping separator, this method will + * only ever return the first occurrence. Use unumf_resultGetAllFields() to access all occurrences of an + * attribute. * - * @draft ICU 62 + * @param fpos + * A pointer to a UFieldPosition. On input, position->field is read. On output, + * position->beginIndex and position->endIndex indicate the beginning and ending indices of field + * number position->field, if such a field exists. */ U_DRAFT void U_EXPORT2 -unumf_closeResult(const UFormattedNumber* uresult, UErrorCode* ec); +unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); /** - * Releases the UNumberFormatter created by unumf_openFromSkeletonAndLocale. + * Populates the given iterator with all fields in the formatted output string. This allows you to + * determine the locations of the integer part, fraction part, and sign. + * + * If you need information on only one field, consider using unumf_resultGetField(). + * + * @param fpositer + * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration + * information already present in the UFieldPositionIterator is deleted, and the iterator is reset + * to apply to the fields in the formatted string created by this function call. The field values + * and indexes returned by {@link #ufieldpositer_next} represent fields denoted by + * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot + * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a + * grouping separator field for ',' and an integer field encompassing the entire string. + */ +U_DRAFT void U_EXPORT2 +unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec); + + +/** + * Releases the UNumberFormatter created by unumf_openFromSkeletonAndLocale(). * * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. * * @draft ICU 62 */ U_DRAFT void U_EXPORT2 -unumf_close(UNumberFormatter* uformatter, UErrorCode* ec); +unumf_close(UNumberFormatter* uformatter); + + +/** + * Releases the UFormattedNumber created by unumf_openResult(). + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_closeResult(const UFormattedNumber* uresult); #endif //__UNUMBERFORMATTER_H__ diff --git a/icu4c/source/test/cintltst/cintltst.c b/icu4c/source/test/cintltst/cintltst.c index 2df10537330..20ee1d8295a 100644 --- a/icu4c/source/test/cintltst/cintltst.c +++ b/icu4c/source/test/cintltst/cintltst.c @@ -729,4 +729,18 @@ U_CFUNC UBool assertUEquals(const char* message, const UChar* expected, return TRUE; } +U_CFUNC UBool assertIntEquals(const char* message, int64_t expected, int64_t actual) { + if (expected != actual) { + log_err("FAIL: %s; got \"%d\"; expected \"%d\"\n", + message, actual, expected); + return FALSE; + } +#ifdef VERBOSE_ASSERTIONS + else { + log_verbose("Ok: %s; got \"%d\"\n", message, actual); + } +#endif + return TRUE; +} + #endif diff --git a/icu4c/source/test/cintltst/cintltst.h b/icu4c/source/test/cintltst/cintltst.h index 7540aa1bc04..edb60eb58e8 100644 --- a/icu4c/source/test/cintltst/cintltst.h +++ b/icu4c/source/test/cintltst/cintltst.h @@ -142,6 +142,11 @@ U_CFUNC UBool assertEquals(const char* msg, const char* expectedString, U_CFUNC UBool assertUEquals(const char* msg, const UChar* expectedString, const UChar* actualString); +/** + * Assert that two 64-bit integers are equal, returning TRUE if they do. + */ +U_CFUNC UBool assertIntEquals(const char* msg, int64_t expected, int64_t actual); + /* * note - isICUVersionBefore and isICUVersionAtLeast have been removed. * use log_knownIssue() instead. diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index 03be113b56f..e8ae8b50138 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -10,14 +10,19 @@ #define UNISTR_FROM_STRING_EXPLICIT #include "unicode/unumberformatter.h" +#include "unicode/umisc.h" +#include "unicode/unum.h" #include "cintltst.h" static void TestSkeletonFormatToString(); +static void TestSkeletonFormatToFields(); + void addUNumberFormatterTest(TestNode** root); void addUNumberFormatterTest(TestNode** root) { addTest(root, &TestSkeletonFormatToString, "unumberformatter/TestSkeletonFormatToString"); + addTest(root, &TestSkeletonFormatToFields, "unumberformatter/TestSkeletonFormatToFields"); } @@ -26,39 +31,102 @@ static void TestSkeletonFormatToString() { static const int32_t CAPACITY = 30; UChar buffer[CAPACITY]; - // SETUP: + // setup: UNumberFormatter* f = unumf_openFromSkeletonAndLocale( u"round-integer currency/USD sign-accounting", -1, "en", &ec); assertSuccess("Should create without error", &ec); UFormattedNumber* result = unumf_openResult(&ec); assertSuccess("Should create result without error", &ec); - // INT TEST: + // int64 test: unumf_formatInt(f, -444444, result, &ec); assertSuccess("Should format integer without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"($444,444)", buffer); - // DOUBLE TEST: + // double test: unumf_formatDouble(f, -5142.3, result, &ec); assertSuccess("Should format double without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"($5,142)", buffer); - // DECIMAL TEST: + // decnumber test: unumf_formatDecimal(f, "9.876E2", -1, result, &ec); assertSuccess("Should format decimal without error", &ec); unumf_resultToString(result, buffer, CAPACITY, &ec); assertSuccess("Should print string to buffer without error", &ec); assertUEquals("Should produce expected string result", u"$988", buffer); - // CLEANUP: - unumf_closeResult(result, &ec); - assertSuccess("Should close without error", &ec); - unumf_close(f, &ec); - assertSuccess("Should close without error", &ec); + // cleanup: + unumf_closeResult(result); + unumf_close(f); +} + + +static void TestSkeletonFormatToFields() { + UErrorCode ec = U_ZERO_ERROR; + + // setup: + UNumberFormatter* uformatter = unumf_openFromSkeletonAndLocale( + u".00 measure-unit/length-meter sign-always", -1, "en", &ec); + assertSuccess("Should create without error", &ec); + UFormattedNumber* uresult = unumf_openResult(&ec); + assertSuccess("Should create result without error", &ec); + unumf_formatInt(uformatter, 9876543210L, uresult, &ec); // "+9,876,543,210.00 m" + + // field position test: + UFieldPosition ufpos = {UNUM_DECIMAL_SEPARATOR_FIELD}; + unumf_resultGetField(uresult, &ufpos, &ec); + assertIntEquals("Field position should be correct", 14, ufpos.beginIndex); + assertIntEquals("Field position should be correct", 15, ufpos.endIndex); + + // field position iterator test: + UFieldPositionIterator* ufpositer = ufieldpositer_open(&ec); + assertSuccess("Should create iterator without error", &ec); + unumf_resultGetAllFields(uresult, ufpositer, &ec); + static const UFieldPosition expectedFields[] = { + // Field, begin index, end index + {UNUM_SIGN_FIELD, 0, 1}, + {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, + {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, + {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11}, + {UNUM_INTEGER_FIELD, 1, 14}, + {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15}, + {UNUM_FRACTION_FIELD, 15, 17}}; + UFieldPosition actual; + for (int32_t i = 0; i < sizeof(expectedFields) / sizeof(*expectedFields); i++) { + // Iterate using the UFieldPosition to hold state... + UFieldPosition expected = expectedFields[i]; + actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex); + assertTrue("Should not return a negative index yet", actual.field >= 0); + if (expected.field != actual.field) { + log_err( + "FAIL: iteration %d; expected field %d; got %d\n", i, expected.field, actual.field); + } + if (expected.beginIndex != actual.beginIndex) { + log_err( + "FAIL: iteration %d; expected beginIndex %d; got %d\n", + i, + expected.beginIndex, + actual.beginIndex); + } + if (expected.endIndex != actual.endIndex) { + log_err( + "FAIL: iteration %d; expected endIndex %d; got %d\n", + i, + expected.endIndex, + actual.endIndex); + } + } + actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex); + assertTrue("No more fields; should return a negative index", actual.field < 0); + + // cleanup: + unumf_closeResult(uresult); + unumf_close(uformatter); + ufieldpositer_close(ufpositer); }