From: Frank Tang <41213225+FrankYFTang@users.noreply.github.com> Date: Mon, 10 Sep 2018 02:14:16 +0000 (-0700) Subject: ICU-13754 Add a ListFormatter FieldPositionIterator format method (#109) X-Git-Tag: release-63-rc~81 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=00ccb44a302d2f4cc9069934cf4668a981068d23;p=icu ICU-13754 Add a ListFormatter FieldPositionIterator format method (#109) ICU-13754 Reapply PR#106 after sffc rewinding master it. --- diff --git a/icu4c/source/i18n/listformatter.cpp b/icu4c/source/i18n/listformatter.cpp index 042af88eb93..5be41a3a714 100644 --- a/icu4c/source/i18n/listformatter.cpp +++ b/icu4c/source/i18n/listformatter.cpp @@ -16,11 +16,16 @@ * created by: Umesh P. Nair */ +#include "cmemory.h" +#include "unicode/fpositer.h" // FieldPositionIterator #include "unicode/listformatter.h" #include "unicode/simpleformatter.h" +#include "unicode/ulistformatter.h" +#include "fphdlimp.h" #include "mutex.h" #include "hash.h" #include "cstring.h" +#include "uarrsort.h" #include "ulocimp.h" #include "charstr.h" #include "ucln_in.h" @@ -61,14 +66,14 @@ ListFormatInternal(const ListFormatInternal &other) : -static Hashtable* listPatternHash = NULL; +static Hashtable* listPatternHash = nullptr; static UMutex listFormatterMutex = U_MUTEX_INITIALIZER; static const char STANDARD_STYLE[] = "standard"; U_CDECL_BEGIN static UBool U_CALLCONV uprv_listformatter_cleanup() { delete listPatternHash; - listPatternHash = NULL; + listPatternHash = nullptr; return TRUE; } @@ -81,7 +86,7 @@ U_CDECL_END ListFormatter::ListFormatter(const ListFormatter& other) : owned(other.owned), data(other.data) { - if (other.owned != NULL) { + if (other.owned != nullptr) { owned = new ListFormatInternal(*other.owned); data = owned; } @@ -96,7 +101,7 @@ ListFormatter& ListFormatter::operator=(const ListFormatter& other) { owned = new ListFormatInternal(*other.owned); data = owned; } else { - owned = NULL; + owned = nullptr; data = other.data; } return *this; @@ -108,7 +113,7 @@ void ListFormatter::initializeHash(UErrorCode& errorCode) { } listPatternHash = new Hashtable(); - if (listPatternHash == NULL) { + if (listPatternHash == nullptr) { errorCode = U_MEMORY_ALLOCATION_ERROR; return; } @@ -121,40 +126,40 @@ void ListFormatter::initializeHash(UErrorCode& errorCode) { const ListFormatInternal* ListFormatter::getListFormatInternal( const Locale& locale, const char *style, UErrorCode& errorCode) { if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } CharString keyBuffer(locale.getName(), errorCode); keyBuffer.append(':', errorCode).append(style, errorCode); UnicodeString key(keyBuffer.data(), -1, US_INV); - ListFormatInternal* result = NULL; + ListFormatInternal* result = nullptr; { Mutex m(&listFormatterMutex); - if (listPatternHash == NULL) { + if (listPatternHash == nullptr) { initializeHash(errorCode); if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } } result = static_cast(listPatternHash->get(key)); } - if (result != NULL) { + if (result != nullptr) { return result; } result = loadListFormatInternal(locale, style, errorCode); if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } { Mutex m(&listFormatterMutex); ListFormatInternal* temp = static_cast(listPatternHash->get(key)); - if (temp != NULL) { + if (temp != nullptr) { delete result; result = temp; } else { listPatternHash->put(key, result, errorCode); if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } } } @@ -235,11 +240,11 @@ ListFormatter::ListPatternsSink::~ListPatternsSink() {} ListFormatInternal* ListFormatter::loadListFormatInternal( const Locale& locale, const char * style, UErrorCode& errorCode) { - UResourceBundle* rb = ures_open(NULL, locale.getName(), &errorCode); + UResourceBundle* rb = ures_open(nullptr, locale.getName(), &errorCode); rb = ures_getByKeyWithFallback(rb, "listPattern", rb, &errorCode); if (U_FAILURE(errorCode)) { ures_close(rb); - return NULL; + return nullptr; } ListFormatter::ListPatternsSink sink; char currentStyle[kStyleLenMax+1]; @@ -255,20 +260,20 @@ ListFormatInternal* ListFormatter::loadListFormatInternal( } ures_close(rb); if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } if (sink.two.isEmpty() || sink.start.isEmpty() || sink.middle.isEmpty() || sink.end.isEmpty()) { errorCode = U_MISSING_RESOURCE_ERROR; - return NULL; + return nullptr; } ListFormatInternal* result = new ListFormatInternal(sink.two, sink.start, sink.middle, sink.end, errorCode); - if (result == NULL) { + if (result == nullptr) { errorCode = U_MEMORY_ALLOCATION_ERROR; - return NULL; + return nullptr; } if (U_FAILURE(errorCode)) { delete result; - return NULL; + return nullptr; } return result; } @@ -286,12 +291,12 @@ ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *s Locale tempLocale = locale; const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode); if (U_FAILURE(errorCode)) { - return NULL; + return nullptr; } ListFormatter* p = new ListFormatter(listFormatInternal); - if (p == NULL) { + if (p == nullptr) { errorCode = U_MEMORY_ALLOCATION_ERROR; - return NULL; + return nullptr; } return p; } @@ -301,7 +306,7 @@ ListFormatter::ListFormatter(const ListFormatData& listFormatData, UErrorCode &e data = owned; } -ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) { +ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(nullptr), data(listFormatterInternal) { } ListFormatter::~ListFormatter() { @@ -323,6 +328,8 @@ static void joinStringsAndReplace( UnicodeString &result, UBool recordOffset, int32_t &offset, + int32_t *offsetFirst, + int32_t *offsetSecond, UErrorCode& errorCode) { if (U_FAILURE(errorCode)) { return; @@ -348,6 +355,8 @@ static void joinStringsAndReplace( } else if (offset >= 0) { offset += offsets[0]; } + if (offsetFirst != nullptr) *offsetFirst = offsets[0]; + if (offsetSecond != nullptr) *offsetSecond = offsets[1]; } UnicodeString& ListFormatter::format( @@ -359,6 +368,17 @@ UnicodeString& ListFormatter::format( return format(items, nItems, appendTo, -1, offset, errorCode); } +UnicodeString& ListFormatter::format( + const UnicodeString items[], + int32_t nItems, + UnicodeString & appendTo, + FieldPositionIterator* posIter, + UErrorCode& errorCode) const { + int32_t offset; + FieldPositionIteratorHandler handler(posIter, errorCode); + return format_(items, nItems, appendTo, -1, offset, &handler, errorCode); +}; + UnicodeString& ListFormatter::format( const UnicodeString items[], int32_t nItems, @@ -366,11 +386,22 @@ UnicodeString& ListFormatter::format( int32_t index, int32_t &offset, UErrorCode& errorCode) const { + return format_(items, nItems, appendTo, index, offset, nullptr, errorCode); +} + +UnicodeString& ListFormatter::format_( + const UnicodeString items[], + int32_t nItems, + UnicodeString& appendTo, + int32_t index, + int32_t &offset, + FieldPositionHandler* handler, + UErrorCode& errorCode) const { offset = -1; if (U_FAILURE(errorCode)) { return appendTo; } - if (data == NULL) { + if (data == nullptr) { errorCode = U_INVALID_STATE_ERROR; return appendTo; } @@ -382,6 +413,11 @@ UnicodeString& ListFormatter::format( if (index == 0) { offset = appendTo.length(); } + if (handler != nullptr) { + handler->addAttribute(ULISTFMT_ELEMENT_FIELD, + appendTo.length(), + appendTo.length() + items[0].length()); + } appendTo.append(items[0]); return appendTo; } @@ -389,6 +425,12 @@ UnicodeString& ListFormatter::format( if (index == 0) { offset = 0; } + int32_t offsetFirst; + int32_t offsetSecond; + int32_t prefixLength = 0; + // for n items, there are 2 * (n + 1) boundary including 0 and the upper + // edge. + MaybeStackArray offsets((handler != nullptr) ? 2 * (nItems + 1): 0); joinStringsAndReplace( nItems == 2 ? data->twoPattern : data->startPattern, result, @@ -396,7 +438,14 @@ UnicodeString& ListFormatter::format( result, index == 1, offset, + &offsetFirst, + &offsetSecond, errorCode); + if (handler != nullptr) { + offsets[0] = 0; + prefixLength += offsetFirst; + offsets[1] = offsetSecond - prefixLength; + } if (nItems > 2) { for (int32_t i = 2; i < nItems - 1; ++i) { joinStringsAndReplace( @@ -406,7 +455,13 @@ UnicodeString& ListFormatter::format( result, index == i, offset, + &offsetFirst, + &offsetSecond, errorCode); + if (handler != nullptr) { + prefixLength += offsetFirst; + offsets[i] = offsetSecond - prefixLength; + } } joinStringsAndReplace( data->endPattern, @@ -415,7 +470,45 @@ UnicodeString& ListFormatter::format( result, index == nItems - 1, offset, + &offsetFirst, + &offsetSecond, errorCode); + if (handler != nullptr) { + prefixLength += offsetFirst; + offsets[nItems - 1] = offsetSecond - prefixLength; + } + } + if (handler != nullptr) { + // If there are already some data in appendTo, we need to adjust the index + // by shifting that lenght while insert into handler. + int32_t shift = appendTo.length() + prefixLength; + // Output the ULISTFMT_ELEMENT_FIELD in the order of the input elements + for (int32_t i = 0; i < nItems; ++i) { + offsets[i + nItems] = offsets[i] + items[i].length() + shift; + offsets[i] += shift; + handler->addAttribute( + ULISTFMT_ELEMENT_FIELD, // id + offsets[i], // index + offsets[i + nItems]); // limit + } + // The locale pattern may reorder the items (such as in ur-IN locale), + // so we cannot assume the array is in accendning order. + // To handle the edging case, just insert the two ends into the array + // and sort. Then we output ULISTFMT_LITERAL_FIELD if the indecies + // between the even and odd position are not the same in the sorted array. + offsets[2 * nItems] = shift - prefixLength; + offsets[2 * nItems + 1] = result.length() + shift - prefixLength; + uprv_sortArray(offsets.getAlias(), 2 * (nItems + 1), sizeof(int32_t), + uprv_int32Comparator, nullptr, + false, &errorCode); + for (int32_t i = 0; i <= nItems; ++i) { + if (offsets[i * 2] != offsets[i * 2 + 1]) { + handler->addAttribute( + ULISTFMT_LITERAL_FIELD, // id + offsets[i * 2], // index + offsets[i * 2 + 1]); // limit + } + } } if (U_SUCCESS(errorCode)) { if (offset >= 0) { diff --git a/icu4c/source/i18n/unicode/listformatter.h b/icu4c/source/i18n/unicode/listformatter.h index 742fce365d9..d46f01a15fb 100644 --- a/icu4c/source/i18n/unicode/listformatter.h +++ b/icu4c/source/i18n/unicode/listformatter.h @@ -26,6 +26,9 @@ U_NAMESPACE_BEGIN +class FieldPositionIterator; +class FieldPositionHandler; + /** @internal */ class Hashtable; @@ -137,6 +140,27 @@ class U_I18N_API ListFormatter : public UObject{ UnicodeString& format(const UnicodeString items[], int32_t n_items, UnicodeString& appendTo, UErrorCode& errorCode) const; +#ifndef U_HIDE_DRAFT_API + /** + * Format a list of strings. + * + * @param items An array of strings to be combined and formatted. + * @param n_items Length of the array items. + * @param appendTo The string to which the formatted result will be + * appended. + * @param posIter On return, can be used to iterate over positions of + * fields generated by this format call. Field values are + * defined in UListFormatterField. Can be NULL. + * @param status ICU error code returned here. + * @return Formatted string combining the elements of items, + * appended to appendTo. + * @draft ICU 63 + */ + UnicodeString& format(const UnicodeString items[], int32_t n_items, + UnicodeString & appendTo, FieldPositionIterator* posIter, + UErrorCode& errorCode) const; +#endif /* U_HIDE_DRAFT_API */ + #ifndef U_HIDE_INTERNAL_API /** @internal for MeasureFormat @@ -164,6 +188,10 @@ class U_I18N_API ListFormatter : public UObject{ struct ListPatternsSink; static ListFormatInternal* loadListFormatInternal(const Locale& locale, const char* style, UErrorCode& errorCode); + UnicodeString& format_( + const UnicodeString items[], int32_t n_items, UnicodeString& appendTo, + int32_t index, int32_t &offset, FieldPositionHandler* handler, UErrorCode& errorCode) const; + ListFormatter(); ListFormatInternal* owned; diff --git a/icu4c/source/i18n/unicode/ulistformatter.h b/icu4c/source/i18n/unicode/ulistformatter.h index 9fe24f7c7b5..d794a894047 100644 --- a/icu4c/source/i18n/unicode/ulistformatter.h +++ b/icu4c/source/i18n/unicode/ulistformatter.h @@ -33,6 +33,26 @@ struct UListFormatter; typedef struct UListFormatter UListFormatter; /**< C typedef for struct UListFormatter. @stable ICU 55 */ +#ifndef U_HIDE_DRAFT_API +/** + * FieldPosition and UFieldPosition selectors for format fields + * defined by ListFormatter. + * @draft ICU 63 + */ +typedef enum UListFormatterField { + /** + * The literal text in the result which came from the resources. + * @draft ICU 63 + */ + ULISTFMT_LITERAL_FIELD, + /** + * The element text in the result which came from the input strings. + * @draft ICU 63 + */ + ULISTFMT_ELEMENT_FIELD +} UListFormatterField; +#endif // U_HIDE_DRAFT_API + /** * Open a new UListFormatter object using the rules for a given locale. * @param locale diff --git a/icu4c/source/test/intltest/listformattertest.cpp b/icu4c/source/test/intltest/listformattertest.cpp index af49baa05dd..2590b30ed49 100644 --- a/icu4c/source/test/intltest/listformattertest.cpp +++ b/icu4c/source/test/intltest/listformattertest.cpp @@ -17,8 +17,61 @@ */ #include "listformattertest.h" +#include "unicode/ulistformatter.h" #include +namespace { +const char* attrString(int32_t attrId) { + switch (attrId) { + case ULISTFMT_LITERAL_FIELD: return "literal"; + case ULISTFMT_ELEMENT_FIELD: return "element"; + default: return "xxx"; + } +} +} // namespace + +void ListFormatterTest::ExpectPositions(FieldPositionIterator& iter, + int32_t *values, int32_t tupleCount) { + UBool found[10]; + FieldPosition fp; + if (tupleCount > 10) { + assertTrue("internal error, tupleCount too large", FALSE); + } else { + for (int i = 0; i < tupleCount; ++i) { + found[i] = FALSE; + } + } + while (iter.next(fp)) { + UBool ok = FALSE; + int32_t id = fp.getField(); + int32_t start = fp.getBeginIndex(); + int32_t limit = fp.getEndIndex(); + char buf[128]; + sprintf(buf, "%24s %3d %3d %3d", attrString(id), id, start, limit); + logln(buf); + for (int i = 0; i < tupleCount; ++i) { + if (found[i]) { + continue; + } + if (values[i*3] == id && values[i*3+1] == start && values[i*3+2] == limit) { + found[i] = ok = TRUE; + break; + } + } + assertTrue((UnicodeString)"found [" + attrString(id) + "," + start + "," + limit + "]", ok); + } + // check that all were found + UBool ok = TRUE; + for (int i = 0; i < tupleCount; ++i) { + if (!found[i]) { + ok = FALSE; + assertTrue((UnicodeString) "missing [" + attrString(values[i*3]) + "," + values[i*3+1] + + "," + values[i*3+2] + "]", found[i]); + } + } + assertTrue("no expected values were missing", ok); +} + ListFormatterTest::ListFormatterTest() : prefix("Prefix: ", -1, US_INV), one("Alice", -1, US_INV), two("Bob", -1, US_INV), @@ -26,9 +79,9 @@ ListFormatterTest::ListFormatterTest() : } void ListFormatterTest::CheckFormatting(const ListFormatter* formatter, UnicodeString data[], int32_t dataSize, - const UnicodeString& expected_result) { + const UnicodeString& expected_result, const char* testName) { UnicodeString actualResult(prefix); - UErrorCode errorCode = U_ZERO_ERROR; + IcuTestErrorCode errorCode(*this, testName); formatter->format(data, dataSize, actualResult, errorCode); UnicodeString expectedStringWithPrefix = prefix + expected_result; if (expectedStringWithPrefix != actualResult) { @@ -37,29 +90,29 @@ void ListFormatterTest::CheckFormatting(const ListFormatter* formatter, UnicodeS } void ListFormatterTest::CheckFourCases(const char* locale_string, UnicodeString one, UnicodeString two, - UnicodeString three, UnicodeString four, UnicodeString results[4]) { - UErrorCode errorCode = U_ZERO_ERROR; + UnicodeString three, UnicodeString four, UnicodeString results[4], const char* testName) { + IcuTestErrorCode errorCode(*this, testName); LocalPointer formatter(ListFormatter::createInstance(Locale(locale_string), errorCode)); if (U_FAILURE(errorCode)) { dataerrln("ListFormatter::createInstance(Locale(\"%s\"), errorCode) failed in CheckFourCases: %s", locale_string, u_errorName(errorCode)); return; } UnicodeString input1[] = {one}; - CheckFormatting(formatter.getAlias(), input1, 1, results[0]); + CheckFormatting(formatter.getAlias(), input1, 1, results[0], testName); UnicodeString input2[] = {one, two}; - CheckFormatting(formatter.getAlias(), input2, 2, results[1]); + CheckFormatting(formatter.getAlias(), input2, 2, results[1], testName); UnicodeString input3[] = {one, two, three}; - CheckFormatting(formatter.getAlias(), input3, 3, results[2]); + CheckFormatting(formatter.getAlias(), input3, 3, results[2], testName); UnicodeString input4[] = {one, two, three, four}; - CheckFormatting(formatter.getAlias(), input4, 4, results[3]); + CheckFormatting(formatter.getAlias(), input4, 4, results[3], testName); } UBool ListFormatterTest::RecordFourCases(const Locale& locale, UnicodeString one, UnicodeString two, - UnicodeString three, UnicodeString four, UnicodeString results[4]) { - UErrorCode errorCode = U_ZERO_ERROR; + UnicodeString three, UnicodeString four, UnicodeString results[4], const char* testName) { + IcuTestErrorCode errorCode(*this, testName); LocalPointer formatter(ListFormatter::createInstance(locale, errorCode)); if (U_FAILURE(errorCode)) { dataerrln("ListFormatter::createInstance(\"%s\", errorCode) failed in RecordFourCases: %s", locale.getName(), u_errorName(errorCode)); @@ -88,14 +141,14 @@ void ListFormatterTest::TestRoot() { one + ", " + two + ", " + three + ", " + four }; - CheckFourCases("", one, two, three, four, results); + CheckFourCases("", one, two, three, four, results, "TestRoot()"); } // Bogus locale should fallback to root. void ListFormatterTest::TestBogus() { UnicodeString results[4]; - if (RecordFourCases(Locale::getDefault(), one, two, three, four, results)) { - CheckFourCases("ex_PY", one, two, three, four, results); + if (RecordFourCases(Locale::getDefault(), one, two, three, four, results, "TestBogus()")) { + CheckFourCases("ex_PY", one, two, three, four, results, "TestBogus()"); } } @@ -109,11 +162,11 @@ void ListFormatterTest::TestEnglish() { one + ", " + two + ", " + three + ", and " + four }; - CheckFourCases("en", one, two, three, four, results); + CheckFourCases("en", one, two, three, four, results, "TestEnglish()"); } void ListFormatterTest::Test9946() { - UErrorCode errorCode = U_ZERO_ERROR; + IcuTestErrorCode errorCode(*this, "Test9946()"); LocalPointer formatter(ListFormatter::createInstance(Locale("en"), errorCode)); if (U_FAILURE(errorCode)) { dataerrln( @@ -144,7 +197,7 @@ void ListFormatterTest::TestEnglishUS() { one + ", " + two + ", " + three + ", and " + four }; - CheckFourCases("en_US", one, two, three, four, results); + CheckFourCases("en_US", one, two, three, four, results, "TestEnglishUS()"); } // Tests resource loading and inheritance when region sublocale @@ -158,7 +211,231 @@ void ListFormatterTest::TestEnglishGB() { one + ", " + two + ", " + three + " and " + four }; - CheckFourCases("en_GB", one, two, three, four, results); + CheckFourCases("en_GB", one, two, three, four, results, "TestEnglishGB()"); +} + +void ListFormatterTest::TestFieldPositionIteratorWontCrash() { + IcuTestErrorCode errorCode(*this, "TestFieldPositionIteratorWontCrash()"); + LocalPointer formatter( + ListFormatter::createInstance(Locale("en"), errorCode)); + if (U_FAILURE(errorCode)) { + dataerrln( + "ListFormatter::createInstance(Locale(\"en\"), errorCode) failed in " + "TestFieldPositionIteratorWontCrash: %s", + u_errorName(errorCode)); + return; + } + UnicodeString data[3] = {"a", "bbb", "cc"}; + UnicodeString actualResult; + // make sure NULL as FieldPositionIterator won't caused crash. + formatter->format(data, 3, actualResult, nullptr, errorCode); + if (U_FAILURE(errorCode)) { + dataerrln( + "ListFormatter::format(data, 3, nullptr, errorCode) " + "failed in TestFieldPositionIteratorWontCrash: %s", + u_errorName(errorCode)); + return; + } +} + +void ListFormatterTest::RunTestFieldPositionIteratorWithFormatter( + ListFormatter* formatter, + UnicodeString data[], int32_t n, int32_t expected[], int32_t tupleCount, + UnicodeString& appendTo, const char16_t *expectedFormatted, + const char* testName) { + IcuTestErrorCode errorCode(*this, testName); + FieldPositionIterator iter; + formatter->format(data, n, appendTo, &iter, errorCode); + if (U_FAILURE(errorCode)) { + dataerrln( + "ListFormatter::format(data, %d, &iter, errorCode) " + "failed in %s: %s", n, testName, u_errorName(errorCode)); + return; + } + if (appendTo != expectedFormatted) { + errln(UnicodeString("Expected: |") + expectedFormatted + "|, Actual: |" + appendTo + "|"); + } + ExpectPositions(iter, expected, tupleCount); +} + +void ListFormatterTest::RunTestFieldPositionIteratorWithNItemsPatternShift( + UnicodeString data[], int32_t n, int32_t expected[], int32_t tupleCount, + UnicodeString& appendTo, const char16_t *expectedFormatted, + const char* testName) { + IcuTestErrorCode errorCode(*this, testName); + LocalPointer formatter( + ListFormatter::createInstance(Locale("ur", "IN"), "unit-narrow", errorCode)); + if (U_FAILURE(errorCode)) { + dataerrln( + "ListFormatter::createInstance(Locale(\"ur\", \"IN\"), \"unit-narrow\", errorCode) failed in " + "%s: %s", testName, u_errorName(errorCode)); + return; + } + RunTestFieldPositionIteratorWithFormatter( + formatter.getAlias(), + data, n, expected, tupleCount, appendTo, expectedFormatted, testName); +} + +void ListFormatterTest::RunTestFieldPositionIteratorWithNItems( + UnicodeString data[], int32_t n, int32_t expected[], int32_t tupleCount, + UnicodeString& appendTo, const char16_t *expectedFormatted, + const char* testName) { + IcuTestErrorCode errorCode(*this, testName); + LocalPointer formatter( + ListFormatter::createInstance(Locale("en"), errorCode)); + if (U_FAILURE(errorCode)) { + dataerrln( + "ListFormatter::createInstance(Locale(\"en\"), errorCode) failed in " + "%s: %s", testName, u_errorName(errorCode)); + return; + } + RunTestFieldPositionIteratorWithFormatter( + formatter.getAlias(), + data, n, expected, tupleCount, appendTo, expectedFormatted, testName); +} + +void ListFormatterTest::TestFieldPositionIteratorWith3ItemsAndDataBefore() { + // 0 1 2 + // 0123456789012345678901234567 + // "Hello World: a, bbb, and cc" + UnicodeString data[3] = {"a", "bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 13, 14, + ULISTFMT_LITERAL_FIELD, 14, 16, + ULISTFMT_ELEMENT_FIELD, 16, 19, + ULISTFMT_LITERAL_FIELD, 19, 25, + ULISTFMT_ELEMENT_FIELD, 25, 27 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo(u"Hello World: "); + RunTestFieldPositionIteratorWithNItems( + data, 3, expected, tupleCount, appendTo, + u"Hello World: a, bbb, and cc", + "TestFieldPositionIteratorWith3ItemsAndDataBefore"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith3Items() { + // 0 1 + // 012345678901234 + // "a, bbb, and cc" + UnicodeString data[3] = {"a", "bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 0, 1, + ULISTFMT_LITERAL_FIELD, 1, 3, + ULISTFMT_ELEMENT_FIELD, 3, 6, + ULISTFMT_LITERAL_FIELD, 6, 12, + ULISTFMT_ELEMENT_FIELD, 12, 14 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo; + RunTestFieldPositionIteratorWithNItems( + data, 3, expected, tupleCount, appendTo, + u"a, bbb, and cc", + "TestFieldPositionIteratorWith3Items"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith3ItemsPatternShift() { + // 0 1 + // 012345678901234 + // "cc bbb a" + UnicodeString data[3] = {"a", "bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 7, 8, + ULISTFMT_LITERAL_FIELD, 6, 7, + ULISTFMT_ELEMENT_FIELD, 3, 6, + ULISTFMT_LITERAL_FIELD, 2, 3, + ULISTFMT_ELEMENT_FIELD, 0, 2 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo; + RunTestFieldPositionIteratorWithNItemsPatternShift( + data, 3, expected, tupleCount, appendTo, + u"cc bbb a", + "TestFieldPositionIteratorWith3ItemsPatternShift"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith2ItemsAndDataBefore() { + // 0 1 + // 0123456789012345 + // "Foo: bbb and cc" + UnicodeString data[2] = {"bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 5, 8, + ULISTFMT_LITERAL_FIELD, 8, 13, + ULISTFMT_ELEMENT_FIELD, 13, 15 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo("Foo: "); + RunTestFieldPositionIteratorWithNItems( + data, 2, expected, tupleCount, appendTo, + u"Foo: bbb and cc", + "TestFieldPositionIteratorWith2ItemsAndDataBefore"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith2Items() { + // 0 1 + // 01234567890 + // "bbb and cc" + UnicodeString data[2] = {"bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 0, 3, + ULISTFMT_LITERAL_FIELD, 3, 8, + ULISTFMT_ELEMENT_FIELD, 8, 10 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo; + RunTestFieldPositionIteratorWithNItems( + data, 2, expected, tupleCount, appendTo, + u"bbb and cc", + "TestFieldPositionIteratorWith2Items"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith2ItemsPatternShift() { + // 0 1 + // 01234567890 + // "cc bbb" + UnicodeString data[2] = {"bbb", "cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 3, 6, + ULISTFMT_LITERAL_FIELD, 2, 3, + ULISTFMT_ELEMENT_FIELD, 0, 2 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo; + RunTestFieldPositionIteratorWithNItemsPatternShift( + data, 2, expected, tupleCount, appendTo, + u"cc bbb", + "TestFieldPositionIteratorWith2ItemsPatternShift"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith1ItemAndDataBefore() { + // 012345678 + // "Hello cc" + UnicodeString data[1] = {"cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 6, 8 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo("Hello "); + RunTestFieldPositionIteratorWithNItems( + data, 1, expected, tupleCount, appendTo, + u"Hello cc", + "TestFieldPositionIteratorWith1ItemAndDataBefore"); +} + +void ListFormatterTest::TestFieldPositionIteratorWith1Item() { + // 012 + // "cc" + UnicodeString data[1] = {"cc"}; + int32_t expected[] = { + ULISTFMT_ELEMENT_FIELD, 0, 2 + }; + int32_t tupleCount = sizeof(expected)/(3 * sizeof(*expected)); + UnicodeString appendTo; + RunTestFieldPositionIteratorWithNItems( + data, 1, expected, tupleCount, appendTo, + u"cc", + "TestFieldPositionIteratorWith1Item"); } // Tests resource loading and inheritance when region sublocale @@ -172,7 +449,7 @@ void ListFormatterTest::TestNynorsk() { one + ", " + two + ", " + three + " og " + four }; - CheckFourCases("nn", one, two, three, four, results); + CheckFourCases("nn", one, two, three, four, results, "TestNynorsk()"); } // Tests resource loading and inheritance when region sublocale @@ -188,7 +465,7 @@ void ListFormatterTest::TestChineseTradHK() { one + comma_string + two + comma_string + three + and_string + four }; - CheckFourCases("zh_Hant_HK", one, two, three, four, results); + CheckFourCases("zh_Hant_HK", one, two, three, four, results, "TestChineseTradHK()"); } // Formatting in Russian. @@ -202,7 +479,7 @@ void ListFormatterTest::TestRussian() { one + ", " + two + ", " + three + and_string + four }; - CheckFourCases("ru", one, two, three, four, results); + CheckFourCases("ru", one, two, three, four, results, "TestRussian()"); } // Formatting in Malayalam. @@ -219,7 +496,7 @@ void ListFormatterTest::TestMalayalam() { one + ", " + two + ", " + three + ", " + four + total_string }; - CheckFourCases("ml", one, two, three, four, results); + CheckFourCases("ml", one, two, three, four, results, "TestMalayalam()"); } // Formatting in Zulu. @@ -232,7 +509,7 @@ void ListFormatterTest::TestZulu() { one + ", " + two + ", " + three + ", ne-" + four }; - CheckFourCases("zu", one, two, three, four, results); + CheckFourCases("zu", one, two, three, four, results, "TestZulu()"); } void ListFormatterTest::TestOutOfOrderPatterns() { @@ -243,22 +520,22 @@ void ListFormatterTest::TestOutOfOrderPatterns() { four + " in the last after " + three + " after " + two + " after the first " + one }; - UErrorCode errorCode = U_ZERO_ERROR; + IcuTestErrorCode errorCode(*this, "TestOutOfOrderPatterns()"); ListFormatData data("{1} after {0}", "{1} after the first {0}", "{1} after {0}", "{1} in the last after {0}"); ListFormatter formatter(data, errorCode); UnicodeString input1[] = {one}; - CheckFormatting(&formatter, input1, 1, results[0]); + CheckFormatting(&formatter, input1, 1, results[0], "TestOutOfOrderPatterns()"); UnicodeString input2[] = {one, two}; - CheckFormatting(&formatter, input2, 2, results[1]); + CheckFormatting(&formatter, input2, 2, results[1], "TestOutOfOrderPatterns()"); UnicodeString input3[] = {one, two, three}; - CheckFormatting(&formatter, input3, 3, results[2]); + CheckFormatting(&formatter, input3, 3, results[2], "TestOutOfOrderPatterns()"); UnicodeString input4[] = {one, two, three, four}; - CheckFormatting(&formatter, input4, 4, results[3]); + CheckFormatting(&formatter, input4, 4, results[3], "TestOutOfOrderPatterns()"); } void ListFormatterTest::runIndexedTest(int32_t index, UBool exec, @@ -276,7 +553,33 @@ void ListFormatterTest::runIndexedTest(int32_t index, UBool exec, case 9: name = "TestEnglishGB"; if (exec) TestEnglishGB(); break; case 10: name = "TestNynorsk"; if (exec) TestNynorsk(); break; case 11: name = "TestChineseTradHK"; if (exec) TestChineseTradHK(); break; - + case 12: name = "TestFieldPositionIteratorWontCrash"; + if (exec) TestFieldPositionIteratorWontCrash(); + break; + case 13: name = "TestFieldPositionIteratorWith1Item"; + if (exec) TestFieldPositionIteratorWith1Item(); + break; + case 14: name = "TestFieldPositionIteratorWith1ItemAndDataBefore"; + if (exec) TestFieldPositionIteratorWith1ItemAndDataBefore(); + break; + case 15: name = "TestFieldPositionIteratorWith2Items"; + if (exec) TestFieldPositionIteratorWith2Items(); + break; + case 16: name = "TestFieldPositionIteratorWith2ItemsAndDataBefore"; + if (exec) TestFieldPositionIteratorWith2ItemsAndDataBefore(); + break; + case 17: name = "TestFieldPositionIteratorWith2ItemsPatternShift"; + if (exec) TestFieldPositionIteratorWith2ItemsPatternShift(); + break; + case 18: name = "TestFieldPositionIteratorWith3Items"; + if (exec) TestFieldPositionIteratorWith3Items(); + break; + case 19: name = "TestFieldPositionIteratorWith3ItemsAndDataBefore"; + if (exec) TestFieldPositionIteratorWith3ItemsAndDataBefore(); + break; + case 20: name = "TestFieldPositionIteratorWith3ItemsPatternShift"; + if (exec) TestFieldPositionIteratorWith3ItemsPatternShift(); + break; default: name = ""; break; } } diff --git a/icu4c/source/test/intltest/listformattertest.h b/icu4c/source/test/intltest/listformattertest.h index a59a7e01897..61aad7ca614 100644 --- a/icu4c/source/test/intltest/listformattertest.h +++ b/icu4c/source/test/intltest/listformattertest.h @@ -19,6 +19,7 @@ #ifndef __LISTFORMATTERTEST_H__ #define __LISTFORMATTERTEST_H__ +#include "unicode/fpositer.h" #include "unicode/listformatter.h" #include "intltest.h" @@ -41,23 +42,68 @@ class ListFormatterTest : public IntlTest { void TestZulu(); void TestOutOfOrderPatterns(); void Test9946(); + void TestFieldPositionIteratorWontCrash(); + void TestFieldPositionIteratorWith1Item(); + void TestFieldPositionIteratorWith2Items(); + void TestFieldPositionIteratorWith3Items(); + void TestFieldPositionIteratorWith1ItemAndDataBefore(); + void TestFieldPositionIteratorWith2ItemsAndDataBefore(); + void TestFieldPositionIteratorWith3ItemsAndDataBefore(); + void TestFieldPositionIteratorWith2ItemsPatternShift(); + void TestFieldPositionIteratorWith3ItemsPatternShift(); private: - void CheckFormatting(const ListFormatter* formatter, UnicodeString data[], int32_t data_size, const UnicodeString& expected_result); + void CheckFormatting( + const ListFormatter* formatter, + UnicodeString data[], + int32_t data_size, + const UnicodeString& expected_result, + const char* testName); + void ExpectPositions( + FieldPositionIterator& iter, + int32_t *values, + int32_t tupleCount); + void RunTestFieldPositionIteratorWithNItems( + UnicodeString *data, + int32_t n, + int32_t *values, + int32_t tupleCount, + UnicodeString& appendTo, + const char16_t *expectedFormatted, + const char* testName); + void RunTestFieldPositionIteratorWithNItemsPatternShift( + UnicodeString *data, + int32_t n, + int32_t *values, + int32_t tupleCount, + UnicodeString& appendTo, + const char16_t *expectedFormatted, + const char* testName); + void RunTestFieldPositionIteratorWithFormatter( + ListFormatter* formatter, + UnicodeString *data, + int32_t n, + int32_t *values, + int32_t tupleCount, + UnicodeString& appendTo, + const char16_t *expectedFormatted, + const char* testName); void CheckFourCases( const char* locale_string, UnicodeString one, UnicodeString two, UnicodeString three, UnicodeString four, - UnicodeString results[4]); + UnicodeString results[4], + const char* testName); UBool RecordFourCases( const Locale& locale, UnicodeString one, UnicodeString two, UnicodeString three, UnicodeString four, - UnicodeString results[4]); + UnicodeString results[4], + const char* testName); private: // Reused test data.