]> granicus.if.org Git - icu/commitdiff
ICU-13754 Add a ListFormatter FieldPositionIterator format method (#109)
authorFrank Tang <41213225+FrankYFTang@users.noreply.github.com>
Mon, 10 Sep 2018 02:14:16 +0000 (19:14 -0700)
committerShane Carr <shane@unicode.org>
Thu, 27 Sep 2018 21:27:39 +0000 (14:27 -0700)
ICU-13754 Reapply PR#106 after sffc rewinding master it.

icu4c/source/i18n/listformatter.cpp
icu4c/source/i18n/unicode/listformatter.h
icu4c/source/i18n/unicode/ulistformatter.h
icu4c/source/test/intltest/listformattertest.cpp
icu4c/source/test/intltest/listformattertest.h

index 042af88eb931553b2ab837670e89cd4f447b9862..5be41a3a714e1fe6381644737b4d7c69bbe2710d 100644 (file)
 *   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<ListFormatInternal*>(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<ListFormatInternal*>(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<int32_t, 10> 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) {
index 742fce365d9a7c9670fcf8bf474fa3727f5e4852..d46f01a15fb7b27e42555b38d762f8b0adc2d0a2 100644 (file)
@@ -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;
index 9fe24f7c7b5efcbda812d629532c86eddab53524..d794a8940473385449de4e07029e4b6a11c9cd9f 100644 (file)
 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
index af49baa05dd51cb9b69a76384b6bc49607592ea0..2590b30ed495042f64c055f4a1a9a30c2e9190a4 100644 (file)
 */
 
 #include "listformattertest.h"
+#include "unicode/ulistformatter.h"
 #include <string.h>
 
+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<ListFormatter> 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<ListFormatter> 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<ListFormatter> 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<ListFormatter> 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<ListFormatter> 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<ListFormatter> 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;
     }
 }
index a59a7e018972dee6d4101934467ca79dc583c295..61aad7ca61434142c389565602dff32c7bc96e78 100644 (file)
@@ -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.