From 9bb910b8d0acf45b3ac00333f299da0b0af4246d Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Mon, 4 Feb 2019 14:21:41 -0800 Subject: [PATCH] ICU-20138 Implementing ufmtval_nextPosition and additional test infra. - Adds test infra for multi-category formatted values. - Adds helper method ConstrainedFieldPosition#matchesField, currently internal. --- icu4c/source/i18n/formattedvalue.cpp | 27 +++++ icu4c/source/i18n/unicode/formattedvalue.h | 3 + icu4c/source/i18n/unicode/uformattedvalue.h | 5 + icu4c/source/test/cintltst/cformtst.h | 17 +++- .../source/test/cintltst/uformattedvaluetst.c | 76 ++++++++++++-- .../test/cintltst/unumberformattertst.c | 10 +- .../test/intltest/formattedvaluetest.cpp | 98 +++++++++++++++---- icu4c/source/test/intltest/itformat.h | 15 ++- icu4c/source/test/intltest/numbertest_api.cpp | 9 -- .../icu/text/ConstrainedFieldPosition.java | 16 +++ .../dev/test/format/FormattedValueTest.java | 9 +- 11 files changed, 239 insertions(+), 46 deletions(-) diff --git a/icu4c/source/i18n/formattedvalue.cpp b/icu4c/source/i18n/formattedvalue.cpp index fa1888994f2..219834a3020 100644 --- a/icu4c/source/i18n/formattedvalue.cpp +++ b/icu4c/source/i18n/formattedvalue.cpp @@ -51,6 +51,19 @@ void ConstrainedFieldPosition::setState( fLimit = limit; } +UBool ConstrainedFieldPosition::matchesField(UFieldCategory category, int32_t field) { + switch (fConstraint) { + case UCFPOS_CONSTRAINT_NONE: + return TRUE; + case UCFPOS_CONSTRAINT_CATEGORY: + return fCategory == category; + case UCFPOS_CONSTRAINT_FIELD: + return fCategory == category && fField == field; + default: + UPRV_UNREACHABLE; + } +} + FormattedValue::~FormattedValue() = default; @@ -200,6 +213,20 @@ ufmtval_getString( } +U_DRAFT UBool U_EXPORT2 +ufmtval_nextPosition( + const UFormattedValue* ufmtval, + UConstrainedFieldPosition* ucfpos, + UErrorCode* ec) { + const auto* fmtval = number::impl::UFormattedValueApiHelper::validate(ufmtval, *ec); + auto* cfpos = UConstrainedFieldPositionImpl::validate(ucfpos, *ec); + if (U_FAILURE(*ec)) { + return FALSE; + } + return fmtval->fFormattedValue->nextPosition(cfpos->fImpl, *ec); +} + + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unicode/formattedvalue.h b/icu4c/source/i18n/unicode/formattedvalue.h index 54593d60689..ee2f615e8ef 100644 --- a/icu4c/source/i18n/unicode/formattedvalue.h +++ b/icu4c/source/i18n/unicode/formattedvalue.h @@ -221,6 +221,9 @@ class U_I18N_API ConstrainedFieldPosition : public UMemory { int32_t start, int32_t limit); + /** @internal */ + UBool matchesField(UFieldCategory category, int32_t field); + private: int64_t fContext = 0LL; int32_t fField = 0; diff --git a/icu4c/source/i18n/unicode/uformattedvalue.h b/icu4c/source/i18n/unicode/uformattedvalue.h index d24ecb2fb2e..dee34e69d97 100644 --- a/icu4c/source/i18n/unicode/uformattedvalue.h +++ b/icu4c/source/i18n/unicode/uformattedvalue.h @@ -54,6 +54,11 @@ typedef enum UFieldCategory { */ UFIELD_CATEGORY_LIST, +#ifndef U_HIDE_INTERNAL_API + /** @internal */ + UFIELD_CATEGORY_COUNT +#endif + } UFieldCategory; diff --git a/icu4c/source/test/cintltst/cformtst.h b/icu4c/source/test/cintltst/cformtst.h index e7c233d525d..bcee4c42f28 100644 --- a/icu4c/source/test/cintltst/cformtst.h +++ b/icu4c/source/test/cintltst/cformtst.h @@ -32,8 +32,14 @@ UChar* myDateFormat(UDateFormat *dat, UDate d); -// The following is implemented in uformattedvaluetest.c -// TODO: When needed, add overload with a different category for each position +typedef struct UFieldPositionWithCategory { + UFieldCategory category; + int32_t field; + int32_t beginIndex; + int32_t endIndex; +} UFieldPositionWithCategory; + +// The following are implemented in uformattedvaluetest.c void checkFormattedValue( const char* message, const UFormattedValue* fv, @@ -42,6 +48,13 @@ void checkFormattedValue( const UFieldPosition* expectedFieldPositions, int32_t expectedFieldPositionsLength); +void checkMixedFormattedValue( + const char* message, + const UFormattedValue* fv, + const UChar* expectedString, + const UFieldPositionWithCategory* expectedFieldPositions, + int32_t length); + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/cintltst/uformattedvaluetst.c b/icu4c/source/test/cintltst/uformattedvaluetst.c index 98a16e19826..a6f4f6d3861 100644 --- a/icu4c/source/test/cintltst/uformattedvaluetst.c +++ b/icu4c/source/test/cintltst/uformattedvaluetst.c @@ -170,23 +170,83 @@ static void AssertAllPartsEqual( } -// Declared in cformtst.h -void checkFormattedValue( +static void checkFormattedValueString( const char* message, const UFormattedValue* fv, const UChar* expectedString, - UFieldCategory expectedCategory, - const UFieldPosition* expectedFieldPositions, - int32_t expectedFieldPositionsLength) { - UErrorCode status = U_ZERO_ERROR; + UErrorCode* ec) { int32_t length; - const UChar* actualString = ufmtval_getString(fv, &length, &status); - assertSuccess(message, &status); + const UChar* actualString = ufmtval_getString(fv, &length, ec); + assertSuccess(message, ec); // The string is guaranteed to be NUL-terminated. int32_t actualLength = u_strlen(actualString); assertIntEquals(message, actualLength, length); assertUEquals(message, expectedString, actualString); } +// Declared in cformtst.h +void checkFormattedValue( + const char* message, + const UFormattedValue* fv, + const UChar* expectedString, + UFieldCategory expectedCategory, + const UFieldPosition* expectedFieldPositions, + int32_t expectedFieldPositionsLength) { + UErrorCode ec = U_ZERO_ERROR; + checkFormattedValueString(message, fv, expectedString, &ec); + if (U_FAILURE(ec)) { return; } + + // Basic loop over the fields (more rigorous testing in C++) + UConstrainedFieldPosition* ucfpos = ucfpos_open(&ec); + int32_t i = 0; + while (ufmtval_nextPosition(fv, ucfpos, &ec)) { + assertIntEquals("category", + expectedCategory, ucfpos_getCategory(ucfpos, &ec)); + assertIntEquals("field", + expectedFieldPositions[i].field, ucfpos_getField(ucfpos, &ec)); + int32_t start, limit; + ucfpos_getIndexes(ucfpos, &start, &limit, &ec); + assertIntEquals("start", + expectedFieldPositions[i].beginIndex, start); + assertIntEquals("limit", + expectedFieldPositions[i].endIndex, limit); + i++; + } + assertTrue("After loop", !ufmtval_nextPosition(fv, ucfpos, &ec)); + assertSuccess("After loop", &ec); + ucfpos_close(ucfpos); +} + +void checkMixedFormattedValue( + const char* message, + const UFormattedValue* fv, + const UChar* expectedString, + const UFieldPositionWithCategory* expectedFieldPositions, + int32_t length) { + UErrorCode ec = U_ZERO_ERROR; + checkFormattedValueString(message, fv, expectedString, &ec); + if (U_FAILURE(ec)) { return; } + + // Basic loop over the fields (more rigorous testing in C++) + UConstrainedFieldPosition* ucfpos = ucfpos_open(&ec); + int32_t i = 0; + while (ufmtval_nextPosition(fv, ucfpos, &ec)) { + assertIntEquals("category", + expectedFieldPositions[i].category, ucfpos_getCategory(ucfpos, &ec)); + assertIntEquals("field", + expectedFieldPositions[i].field, ucfpos_getField(ucfpos, &ec)); + int32_t start, limit; + ucfpos_getIndexes(ucfpos, &start, &limit, &ec); + assertIntEquals("start", + expectedFieldPositions[i].beginIndex, start); + assertIntEquals("limit", + expectedFieldPositions[i].endIndex, limit); + i++; + } + assertTrue("After loop", !ufmtval_nextPosition(fv, ucfpos, &ec)); + assertSuccess("After loop", &ec); + ucfpos_close(ucfpos); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index bbbdfb82a8a..59e65f2afdb 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -211,12 +211,10 @@ static void TestFormattedValue() { assertSuccess("Should convert without error", &ec); static const UFieldPosition expectedFieldPositions[] = { // field, begin index, end index - {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, - {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, - {UNUM_INTEGER_FIELD, 0, 10}, - {UNUM_GROUPING_SEPARATOR_FIELD, 13, 14}, - {UNUM_GROUPING_SEPARATOR_FIELD, 17, 18}, - {UNUM_INTEGER_FIELD, 11, 21}}; + {UNUM_INTEGER_FIELD, 0, 2}, + {UNUM_DECIMAL_SEPARATOR_FIELD, 2, 3}, + {UNUM_FRACTION_FIELD, 3, 5}, + {UNUM_COMPACT_FIELD, 5, 6}}; checkFormattedValue( "FormattedNumber as FormattedValue", fv, diff --git a/icu4c/source/test/intltest/formattedvaluetest.cpp b/icu4c/source/test/intltest/formattedvaluetest.cpp index 1cd1e3c336d..b8a911e7dac 100644 --- a/icu4c/source/test/intltest/formattedvaluetest.cpp +++ b/icu4c/source/test/intltest/formattedvaluetest.cpp @@ -159,7 +159,39 @@ void IntlTestWithFieldPosition::checkFormattedValue( UFieldCategory expectedCategory, const UFieldPosition* expectedFieldPositions, int32_t length) { - IcuTestErrorCode status(*this, "checkFormattedValue"); + LocalArray converted(new UFieldPositionWithCategory[length]); + for (int32_t i=0; i(category)); + for (int32_t i = 0; i < length; i++) { + if (expectedFieldPositions[i].category != category) { + continue; + } + assertTrue(baseMessage + i, fv.nextPosition(cfpos, status)); + int32_t expectedCategory = expectedFieldPositions[i].category; + int32_t expectedField = expectedFieldPositions[i].field; + int32_t expectedStart = expectedFieldPositions[i].beginIndex; + int32_t expectedLimit = expectedFieldPositions[i].endIndex; + assertEquals(baseMessage + u"B category " + Int64ToUnicodeString(i), + expectedCategory, cfpos.getCategory()); + assertEquals(baseMessage + u"B field " + Int64ToUnicodeString(i), + expectedField, cfpos.getField()); + assertEquals(baseMessage + u"B start " + Int64ToUnicodeString(i), + expectedStart, cfpos.getStart()); + assertEquals(baseMessage + u"B limit " + Int64ToUnicodeString(i), + expectedLimit, cfpos.getLimit()); + } + UBool afterLoopResult = fv.nextPosition(cfpos, status); + assertFalse(baseMessage + u"B after loop: " + CFPosToUnicodeString(cfpos), afterLoopResult); + } // Check nextPosition constrained over each field one at a time - std::set uniqueFields; + std::set> uniqueFields; for (int32_t i = 0; i < length; i++) { - uniqueFields.insert(expectedFieldPositions[i].field); + uniqueFields.insert({expectedFieldPositions[i].category, expectedFieldPositions[i].field}); } - for (int32_t field : uniqueFields) { + for (std::pair categoryAndField : uniqueFields) { cfpos.reset(); - cfpos.constrainField(expectedCategory, field); + cfpos.constrainField(categoryAndField.first, categoryAndField.second); for (int32_t i = 0; i < length; i++) { - if (expectedFieldPositions[i].field != field) { + if (expectedFieldPositions[i].category != categoryAndField.first) { + continue; + } + if (expectedFieldPositions[i].field != categoryAndField.second) { continue; } assertTrue(baseMessage + i, fv.nextPosition(cfpos, status)); + int32_t expectedCategory = expectedFieldPositions[i].category; int32_t expectedField = expectedFieldPositions[i].field; int32_t expectedStart = expectedFieldPositions[i].beginIndex; int32_t expectedLimit = expectedFieldPositions[i].endIndex; - assertEquals(baseMessage + u"category " + Int64ToUnicodeString(i), + assertEquals(baseMessage + u"C category " + Int64ToUnicodeString(i), expectedCategory, cfpos.getCategory()); - assertEquals(baseMessage + u"field " + Int64ToUnicodeString(i), + assertEquals(baseMessage + u"C field " + Int64ToUnicodeString(i), expectedField, cfpos.getField()); - assertEquals(baseMessage + u"start " + Int64ToUnicodeString(i), + assertEquals(baseMessage + u"C start " + Int64ToUnicodeString(i), expectedStart, cfpos.getStart()); - assertEquals(baseMessage + u"limit " + Int64ToUnicodeString(i), + assertEquals(baseMessage + u"C limit " + Int64ToUnicodeString(i), expectedLimit, cfpos.getLimit()); } - assertFalse(baseMessage + u"after loop", fv.nextPosition(cfpos, status)); + UBool afterLoopResult = fv.nextPosition(cfpos, status); + assertFalse(baseMessage + u"C after loop: " + CFPosToUnicodeString(cfpos), afterLoopResult); } } diff --git a/icu4c/source/test/intltest/itformat.h b/icu4c/source/test/intltest/itformat.h index 8069b6d9986..d8b17993c30 100644 --- a/icu4c/source/test/intltest/itformat.h +++ b/icu4c/source/test/intltest/itformat.h @@ -26,9 +26,15 @@ class IntlTestFormat: public IntlTest { }; +typedef struct UFieldPositionWithCategory { + UFieldCategory category; + int32_t field; + int32_t beginIndex; + int32_t endIndex; +} UFieldPositionWithCategory; + class IntlTestWithFieldPosition : public IntlTest { public: - // TODO: When needed, add overload with a different category for each position void checkFormattedValue( const char16_t* message, const FormattedValue& fv, @@ -36,6 +42,13 @@ public: UFieldCategory expectedCategory, const UFieldPosition* expectedFieldPositions, int32_t length); + + void checkMixedFormattedValue( + const char16_t* message, + const FormattedValue& fv, + UnicodeString expectedString, + const UFieldPositionWithCategory* expectedFieldPositions, + int32_t length); }; diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index bfa0ba23dbc..b413a202be4 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -2980,15 +2980,6 @@ void NumberFormatterApiTest::assertNumberFieldPositions( expectedFieldPositions, length); - // Check no field positions in an unrelated category - checkFormattedValue( - message, - static_cast(formattedNumber), - formattedNumber.toString(status), - UFIELD_CATEGORY_DATE, - nullptr, - 0); - // Check FormattedNumber-specific functions UnicodeString baseMessage = UnicodeString(message) + u": " + formattedNumber.toString(status) + u": "; FieldPositionIterator fpi; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/ConstrainedFieldPosition.java b/icu4j/main/classes/core/src/com/ibm/icu/text/ConstrainedFieldPosition.java index 7a44f93ea91..998190a1646 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/ConstrainedFieldPosition.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/ConstrainedFieldPosition.java @@ -305,6 +305,22 @@ public class ConstrainedFieldPosition { fLimit = limit; } + /** @internal */ + public boolean matchesField(Field field) { + // If this method ever becomes public, change assert to throw IllegalArgumentException + assert field != null; + switch (fConstraint) { + case NONE: + return true; + case CLASS: + return fClassConstraint.isAssignableFrom(field.getClass()); + case FIELD: + return fField == field; + default: + throw new AssertionError(); + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java index c1a037b364d..fc50106503d 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java @@ -176,7 +176,8 @@ public class FormattedValueTest { assertEquals(baseMessage + "limit " + i, expectedLimit, cfpos.getLimit()); i++; } - assertFalse(baseMessage + "after loop", fv.nextPosition(cfpos)); + boolean afterLoopResult = fv.nextPosition(cfpos); + assertFalse(baseMessage + "after loop: " + cfpos, afterLoopResult); // Check nextPosition constrained over each class one at a time for (Class classConstraint : uniqueFieldClasses) { @@ -196,7 +197,8 @@ public class FormattedValueTest { assertEquals(baseMessage + "limit " + i, expectedLimit, cfpos.getLimit()); i++; } - assertFalse(baseMessage + "after loop", fv.nextPosition(cfpos)); + afterLoopResult = fv.nextPosition(cfpos); + assertFalse(baseMessage + "after loop: " + cfpos, afterLoopResult); } // Check nextPosition constrained over an unrelated class @@ -222,7 +224,8 @@ public class FormattedValueTest { assertEquals(baseMessage + "limit " + i, expectedLimit, cfpos.getLimit()); i++; } - assertFalse(baseMessage + "after loop", fv.nextPosition(cfpos)); + afterLoopResult = fv.nextPosition(cfpos); + assertFalse(baseMessage + "after loop: " + cfpos, afterLoopResult); } } -- 2.40.0