From d4ec281c992ec0ebaca9ace7a075a676e1c42094 Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Wed, 29 Jan 2014 05:08:55 +0000 Subject: [PATCH] ICU-10646 Introduce Template class and change ListFormatter to use it. Introduce QuantityFormatter class and change RelativeDateTimeFormatter to use it. X-SVN-Rev: 35018 --- .gitattributes | 5 + icu4c/source/common/Makefile.in | 2 +- icu4c/source/common/listformatter.cpp | 276 +++++++++++----- icu4c/source/common/template.cpp | 303 ++++++++++++++++++ icu4c/source/common/template.h | 150 +++++++++ icu4c/source/common/unicode/listformatter.h | 23 +- icu4c/source/i18n/Makefile.in | 4 +- icu4c/source/i18n/quantityformatter.cpp | 162 ++++++++++ icu4c/source/i18n/quantityformatter.h | 102 ++++++ icu4c/source/i18n/reldatefmt.cpp | 114 ++----- icu4c/source/test/intltest/Makefile.in | 4 +- icu4c/source/test/intltest/itutil.cpp | 11 +- .../test/intltest/listformattertest.cpp | 4 +- icu4c/source/test/intltest/templatetest.cpp | 202 ++++++++++++ 14 files changed, 1174 insertions(+), 188 deletions(-) create mode 100644 icu4c/source/common/template.cpp create mode 100644 icu4c/source/common/template.h create mode 100644 icu4c/source/i18n/quantityformatter.cpp create mode 100644 icu4c/source/i18n/quantityformatter.h create mode 100644 icu4c/source/test/intltest/templatetest.cpp diff --git a/.gitattributes b/.gitattributes index 66c122c9df1..4dea60aa2d4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -53,6 +53,8 @@ icu4c/source/aclocal.m4 -text icu4c/source/allinone/icucheck.bat -text icu4c/source/common/common.vcxproj -text icu4c/source/common/common.vcxproj.filters -text +icu4c/source/common/template.cpp -text +icu4c/source/common/template.h -text icu4c/source/data/curr/pool.res -text icu4c/source/data/in/coll/invuca.icu -text icu4c/source/data/in/coll/ucadata.icu -text @@ -75,6 +77,8 @@ icu4c/source/extra/uconv/uconv.vcxproj -text icu4c/source/extra/uconv/uconv.vcxproj.filters -text icu4c/source/i18n/i18n.vcxproj -text icu4c/source/i18n/i18n.vcxproj.filters -text +icu4c/source/i18n/quantityformatter.cpp -text +icu4c/source/i18n/quantityformatter.h -text icu4c/source/io/io.vcxproj -text icu4c/source/io/io.vcxproj.filters -text icu4c/source/layout/layout.vcxproj -text @@ -142,6 +146,7 @@ icu4c/source/test/cintltst/cintltst.vcxproj -text icu4c/source/test/cintltst/cintltst.vcxproj.filters -text icu4c/source/test/intltest/intltest.vcxproj -text icu4c/source/test/intltest/intltest.vcxproj.filters -text +icu4c/source/test/intltest/templatetest.cpp -text icu4c/source/test/iotest/iotest.vcxproj -text icu4c/source/test/iotest/iotest.vcxproj.filters -text icu4c/source/test/letest/cletest.vcxproj -text diff --git a/icu4c/source/common/Makefile.in b/icu4c/source/common/Makefile.in index fb9207db208..623a79711a0 100644 --- a/icu4c/source/common/Makefile.in +++ b/icu4c/source/common/Makefile.in @@ -105,7 +105,7 @@ serv.o servnotf.o servls.o servlk.o servlkf.o servrbf.o servslkf.o \ uidna.o usprep.o uts46.o punycode.o \ util.o util_props.o parsepos.o locbased.o cwchar.o wintz.o dtintrv.o ucnvsel.o propsvec.o \ ulist.o uloc_tag.o icudataver.o icuplug.o listformatter.o lrucache.o \ -sharedobject.o +sharedobject.o template.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/common/listformatter.cpp b/icu4c/source/common/listformatter.cpp index f4350d25ccc..240047bca7b 100644 --- a/icu4c/source/common/listformatter.cpp +++ b/icu4c/source/common/listformatter.cpp @@ -1,7 +1,7 @@ /* ******************************************************************************* * -* Copyright (C) 2013, International Business Machines +* Copyright (C) 2013-2014, International Business Machines * Corporation and others. All Rights Reserved. * ******************************************************************************* @@ -15,6 +15,7 @@ */ #include "unicode/listformatter.h" +#include "template.h" #include "mutex.h" #include "hash.h" #include "cstring.h" @@ -23,12 +24,43 @@ #include "ucln_cmn.h" #include "uresimp.h" +#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) + U_NAMESPACE_BEGIN +struct ListFormatInternal : public UMemory { + Template twoPattern; + Template startPattern; + Template middlePattern; + Template endPattern; + +ListFormatInternal( + const UnicodeString& two, + const UnicodeString& start, + const UnicodeString& middle, + const UnicodeString& end) : + twoPattern(two), + startPattern(start), + middlePattern(middle), + endPattern(end) {} + +ListFormatInternal(const ListFormatData &data) : + twoPattern(data.twoPattern), + startPattern(data.startPattern), + middlePattern(data.middlePattern), + endPattern(data.endPattern) { } + +ListFormatInternal(const ListFormatInternal &other) : + twoPattern(other.twoPattern), + startPattern(other.startPattern), + middlePattern(other.middlePattern), + endPattern(other.endPattern) { } +}; + + + static Hashtable* listPatternHash = NULL; static UMutex listFormatterMutex = U_MUTEX_INITIALIZER; -static UChar FIRST_PARAMETER[] = { 0x7b, 0x30, 0x7d }; // "{0}" -static UChar SECOND_PARAMETER[] = { 0x7b, 0x31, 0x7d }; // "{0}" static const char *STANDARD_STYLE = "standard"; U_CDECL_BEGIN @@ -39,20 +71,43 @@ static UBool U_CALLCONV uprv_listformatter_cleanup() { } static void U_CALLCONV -uprv_deleteListFormatData(void *obj) { - delete static_cast(obj); +uprv_deleteListFormatInternal(void *obj) { + delete static_cast(obj); } U_CDECL_END -static ListFormatData* loadListFormatData(const Locale& locale, const char* style, UErrorCode& errorCode); -static void getStringByKey(const UResourceBundle* rb, const char* key, UnicodeString& result, UErrorCode& errorCode); - -ListFormatter::ListFormatter(const ListFormatter& other) : data(other.data) { +static ListFormatInternal* loadListFormatInternal( + const Locale& locale, + const char* style, + UErrorCode& errorCode); + +static void getStringByKey( + const UResourceBundle* rb, + const char* key, + UnicodeString& result, + UErrorCode& errorCode); + +ListFormatter::ListFormatter(const ListFormatter& other) : + owned(other.owned), data(other.data) { + if (other.owned != NULL) { + owned = new ListFormatInternal(*other.owned); + data = owned; + } } ListFormatter& ListFormatter::operator=(const ListFormatter& other) { - data = other.data; + if (this == &other) { + return *this; + } + delete owned; + if (other.owned) { + owned = new ListFormatInternal(*other.owned); + data = owned; + } else { + owned = NULL; + data = other.data; + } return *this; } @@ -67,12 +122,12 @@ void ListFormatter::initializeHash(UErrorCode& errorCode) { return; } - listPatternHash->setValueDeleter(uprv_deleteListFormatData); + listPatternHash->setValueDeleter(uprv_deleteListFormatInternal); ucln_common_registerCleanup(UCLN_COMMON_LIST_FORMATTER, uprv_listformatter_cleanup); } -const ListFormatData* ListFormatter::getListFormatData( +const ListFormatInternal* ListFormatter::getListFormatInternal( const Locale& locale, const char *style, UErrorCode& errorCode) { if (U_FAILURE(errorCode)) { return NULL; @@ -80,7 +135,7 @@ const ListFormatData* ListFormatter::getListFormatData( CharString keyBuffer(locale.getName(), errorCode); keyBuffer.append(':', errorCode).append(style, errorCode); UnicodeString key(keyBuffer.data(), -1, US_INV); - ListFormatData* result = NULL; + ListFormatInternal* result = NULL; { Mutex m(&listFormatterMutex); if (listPatternHash == NULL) { @@ -89,19 +144,19 @@ const ListFormatData* ListFormatter::getListFormatData( return NULL; } } - result = static_cast(listPatternHash->get(key)); + result = static_cast(listPatternHash->get(key)); } if (result != NULL) { return result; } - result = loadListFormatData(locale, style, errorCode); + result = loadListFormatInternal(locale, style, errorCode); if (U_FAILURE(errorCode)) { return NULL; } { Mutex m(&listFormatterMutex); - ListFormatData* temp = static_cast(listPatternHash->get(key)); + ListFormatInternal* temp = static_cast(listPatternHash->get(key)); if (temp != NULL) { delete result; result = temp; @@ -115,7 +170,7 @@ const ListFormatData* ListFormatter::getListFormatData( return result; } -static ListFormatData* loadListFormatData( +static ListFormatInternal* loadListFormatInternal( const Locale& locale, const char * style, UErrorCode& errorCode) { UResourceBundle* rb = ures_open(NULL, locale.getName(), &errorCode); if (U_FAILURE(errorCode)) { @@ -144,7 +199,7 @@ static ListFormatData* loadListFormatData( if (U_FAILURE(errorCode)) { return NULL; } - ListFormatData* result = new ListFormatData(two, start, middle, end); + ListFormatInternal* result = new ListFormatInternal(two, start, middle, end); if (result == NULL) { errorCode = U_MEMORY_ALLOCATION_ERROR; return NULL; @@ -172,11 +227,11 @@ ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& e ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) { Locale tempLocale = locale; - const ListFormatData* listFormatData = getListFormatData(tempLocale, style, errorCode); + const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode); if (U_FAILURE(errorCode)) { return NULL; } - ListFormatter* p = new ListFormatter(listFormatData); + ListFormatter* p = new ListFormatter(listFormatInternal); if (p == NULL) { errorCode = U_MEMORY_ALLOCATION_ERROR; return NULL; @@ -184,83 +239,146 @@ ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *s return p; } - -ListFormatter::ListFormatter(const ListFormatData* listFormatterData) : data(listFormatterData) { +ListFormatter::ListFormatter(const ListFormatData& listFormatData) { + owned = new ListFormatInternal(listFormatData); + data = owned; } -ListFormatter::~ListFormatter() {} - -UnicodeString& ListFormatter::format(const UnicodeString items[], int32_t nItems, - UnicodeString& appendTo, UErrorCode& errorCode) const { - if (U_FAILURE(errorCode)) { - return appendTo; - } - if (data == NULL) { - errorCode = U_INVALID_STATE_ERROR; - return appendTo; - } +ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) { +} - if (nItems > 0) { - UnicodeString newString = items[0]; - if (nItems == 2) { - addNewString(data->twoPattern, newString, items[1], errorCode); - } else if (nItems > 2) { - addNewString(data->startPattern, newString, items[1], errorCode); - int32_t i; - for (i = 2; i < nItems - 1; ++i) { - addNewString(data->middlePattern, newString, items[i], errorCode); - } - addNewString(data->endPattern, newString, items[nItems - 1], errorCode); - } - if (U_SUCCESS(errorCode)) { - appendTo += newString; - } - } - return appendTo; +ListFormatter::~ListFormatter() { + delete owned; } /** - * Joins originalString and nextString using the pattern pat and puts the result in - * originalString. + * Joins first and second using the pattern pat. */ -void ListFormatter::addNewString(const UnicodeString& pat, UnicodeString& originalString, - const UnicodeString& nextString, UErrorCode& errorCode) const { +static void joinStrings( + const Template& pat, + const UnicodeString& first, + const UnicodeString& second, + UnicodeString &result, + UBool recordOffset, + int32_t offset, + UErrorCode& errorCode) { if (U_FAILURE(errorCode)) { return; } - - int32_t p0Offset = pat.indexOf(FIRST_PARAMETER, 3, 0); - if (p0Offset < 0) { - errorCode = U_ILLEGAL_ARGUMENT_ERROR; + const UnicodeString *params[2] = {&first, &second}; + int32_t offsets[2]; + pat.evaluate( + params, + LENGTHOF(params), + result, + offsets, + LENGTHOF(offsets), + errorCode); + if (U_FAILURE(errorCode)) { return; } - int32_t p1Offset = pat.indexOf(SECOND_PARAMETER, 3, 0); - if (p1Offset < 0) { - errorCode = U_ILLEGAL_ARGUMENT_ERROR; + if (offsets[0] == -1 || offsets[1] == -1) { + errorCode = U_INVALID_FORMAT_ERROR; return; } + if (recordOffset) { + offset = offsets[1]; + } else if (offset >= 0) { + offset += offsets[0]; + } +} - int32_t i, j; +UnicodeString& ListFormatter::format( + const UnicodeString items[], + int32_t nItems, + UnicodeString& appendTo, + UErrorCode& errorCode) const { + int32_t offset; + return format(items, nItems, appendTo, -1, offset, errorCode); +} - const UnicodeString* firstString; - const UnicodeString* secondString; - if (p0Offset < p1Offset) { - i = p0Offset; - j = p1Offset; - firstString = &originalString; - secondString = &nextString; - } else { - i = p1Offset; - j = p0Offset; - firstString = &nextString; - secondString = &originalString; +UnicodeString& ListFormatter::format( + const UnicodeString items[], + int32_t nItems, + UnicodeString& appendTo, + int32_t index, + int32_t &offset, + UErrorCode& errorCode) const { + offset = -1; + if (U_FAILURE(errorCode)) { + return appendTo; + } + if (data == NULL) { + errorCode = U_INVALID_STATE_ERROR; + return appendTo; } - UnicodeString result = UnicodeString(pat, 0, i) + *firstString; - result += UnicodeString(pat, i+3, j-i-3); - result += *secondString; - result += UnicodeString(pat, j+3); - originalString = result; + if (nItems <= 0) { + return appendTo; + } + if (nItems == 1) { + if (index == 0) { + offset = appendTo.length(); + } + appendTo.append(items[0]); + return appendTo; + } + if (nItems == 2) { + if (index == 0) { + offset = 0; + } + joinStrings( + data->twoPattern, + items[0], + items[1], + appendTo, + index == 1, + offset, + errorCode); + return appendTo; + } + UnicodeString temp[2]; + if (index == 0) { + offset = 0; + } + joinStrings( + data->startPattern, + items[0], + items[1], + temp[0], + index == 1, + offset, + errorCode); + int32_t i; + int32_t pos = 0; + int32_t npos = 1; + for (i = 2; i < nItems - 1; ++i) { + temp[npos].remove(); + joinStrings( + data->middlePattern, + temp[pos], + items[i], + temp[npos], + index == i, + offset, + errorCode); + pos = npos; + npos = (pos + 1) & 1; + } + temp[npos].remove(); + joinStrings( + data->endPattern, + temp[pos], + items[nItems - 1], + temp[npos], + index == nItems - 1, + offset, + errorCode); + if (U_SUCCESS(errorCode)) { + offset += appendTo.length(); + appendTo += temp[npos]; + } + return appendTo; } U_NAMESPACE_END diff --git a/icu4c/source/common/template.cpp b/icu4c/source/common/template.cpp new file mode 100644 index 00000000000..ac14c26baf8 --- /dev/null +++ b/icu4c/source/common/template.cpp @@ -0,0 +1,303 @@ +/* +****************************************************************************** +* Copyright (C) 2014, International Business Machines +* Corporation and others. All Rights Reserved. +****************************************************************************** +* template.cpp +*/ +#include "template.h" +#include "cstring.h" +#include "uassert.h" + +U_NAMESPACE_BEGIN + +typedef enum TemplateCompileState { + INIT, + APOSTROPHE, + PLACEHOLDER +} TemplateCompileState; + +class TemplateIdBuilder { +public: + TemplateIdBuilder() : id(0), idLen(0) { } + ~TemplateIdBuilder() { } + void reset() { id = 0; idLen = 0; } + int32_t getId() const { return id; } + void appendTo(UChar *buffer, int32_t *len) const; + UBool isValid() const { return (idLen > 0); } + void add(UChar ch); +private: + int32_t id; + int32_t idLen; + TemplateIdBuilder(const TemplateIdBuilder &other); + TemplateIdBuilder &operator=(const TemplateIdBuilder &other); +}; + +void TemplateIdBuilder::appendTo(UChar *buffer, int32_t *len) const { + int32_t origLen = *len; + int32_t kId = id; + for (int32_t i = origLen + idLen - 1; i >= origLen; i--) { + int32_t digit = kId % 10; + buffer[i] = digit + 0x30; + kId /= 10; + } + *len = origLen + idLen; +} + +void TemplateIdBuilder::add(UChar ch) { + id = id * 10 + (ch - 0x30); + idLen++; +} + +Template::Template() : + noPlaceholders(), + placeholdersByOffset(placeholderBuffer), + placeholderSize(0), + placeholderCapacity(EXPECTED_PLACEHOLDER_COUNT), + placeholderCount(0) { +} + +Template::Template(const UnicodeString &pattern) : + noPlaceholders(), + placeholdersByOffset(placeholderBuffer), + placeholderSize(0), + placeholderCapacity(EXPECTED_PLACEHOLDER_COUNT), + placeholderCount(0) { + UErrorCode status = U_ZERO_ERROR; + compile(pattern, status); +} + +Template::Template(const Template &other) : + noPlaceholders(other.noPlaceholders), + placeholdersByOffset(placeholderBuffer), + placeholderSize(0), + placeholderCapacity(EXPECTED_PLACEHOLDER_COUNT), + placeholderCount(other.placeholderCount) { + placeholderSize = ensureCapacity(other.placeholderSize); + uprv_memcpy( + placeholdersByOffset, + other.placeholdersByOffset, + placeholderSize * 2 * sizeof(int32_t)); +} + +Template &Template::operator=(const Template& other) { + if (this == &other) { + return *this; + } + noPlaceholders = other.noPlaceholders; + placeholderCount = other.placeholderCount; + placeholderSize = ensureCapacity(other.placeholderSize); + uprv_memcpy( + placeholdersByOffset, + other.placeholdersByOffset, + placeholderSize * 2 * sizeof(int32_t)); + return *this; +} + +Template::~Template() { + if (placeholdersByOffset != placeholderBuffer) { + uprv_free(placeholdersByOffset); + } +} + +UBool Template::compile(const UnicodeString &pattern, UErrorCode &status) { + if (U_FAILURE(status)) { + return FALSE; + } + const UChar *patternBuffer = pattern.getBuffer(); + int32_t patternLength = pattern.length(); + UChar *buffer = noPlaceholders.getBuffer(patternLength); + int32_t len = 0; + placeholderSize = 0; + placeholderCount = 0; + TemplateCompileState state = INIT; + TemplateIdBuilder idBuilder; + for (int32_t i = 0; i < patternLength; ++i) { + UChar ch = patternBuffer[i]; + switch (state) { + case INIT: + if (ch == 0x27) { + state = APOSTROPHE; + } else if (ch == 0x7B) { + state = PLACEHOLDER; + idBuilder.reset(); + } else { + buffer[len++] = ch; + } + break; + case APOSTROPHE: + if (ch == 0x27) { + buffer[len++] = 0x27; + } else if (ch == 0x7B) { + buffer[len++] = 0x7B; + } else { + buffer[len++] = 0x27; + buffer[len++] = ch; + } + state = INIT; + break; + case PLACEHOLDER: + if (ch >= 0x30 && ch <= 0x39) { + idBuilder.add(ch); + } else if (ch == 0x7D && idBuilder.isValid()) { + if (!addPlaceholder(idBuilder.getId(), len)) { + status = U_MEMORY_ALLOCATION_ERROR; + return FALSE; + } + state = INIT; + } else { + buffer[len++] = 0x7B; + idBuilder.appendTo(buffer, &len); + buffer[len++] = ch; + state = INIT; + } + break; + default: + U_ASSERT(FALSE); + break; + } + } + switch (state) { + case INIT: + break; + case APOSTROPHE: + buffer[len++] = 0x27; + break; + case PLACEHOLDER: + buffer[len++] = 0X7B; + idBuilder.appendTo(buffer, &len); + break; + default: + U_ASSERT(false); + break; + } + noPlaceholders.releaseBuffer(len); + return TRUE; +} + +UnicodeString& Template::evaluate( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &appendTo, + UErrorCode &status) const { + return evaluate( + placeholderValues, + placeholderValueCount, + appendTo, + NULL, + 0, + status); +} + +static void updatePlaceholderOffset( + int32_t placeholderId, + int32_t placeholderOffset, + int32_t *offsetArray, + int32_t offsetArrayLength) { + if (placeholderId < offsetArrayLength) { + offsetArray[placeholderId] = placeholderOffset; + } +} + +static void appendRange( + const UnicodeString &src, + int32_t start, + int32_t end, + UnicodeString &dest) { + dest.append(src, start, end - start); +} + +UnicodeString& Template::evaluate( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &appendTo, + int32_t *offsetArray, + int32_t offsetArrayLength, + UErrorCode &status) const { + if (U_FAILURE(status)) { + return appendTo; + } + if (placeholderValueCount < placeholderCount) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return appendTo; + } + for (int32_t i = 0; i < offsetArrayLength; ++i) { + offsetArray[i] = -1; + } + if (placeholderSize == 0) { + appendTo.append(noPlaceholders); + return appendTo; + } + appendRange( + noPlaceholders, + 0, + placeholdersByOffset[0], + appendTo); + updatePlaceholderOffset( + placeholdersByOffset[1], + appendTo.length(), + offsetArray, + offsetArrayLength); + appendTo.append(*placeholderValues[placeholdersByOffset[1]]); + for (int32_t i = 1; i < placeholderSize; ++i) { + appendRange( + noPlaceholders, + placeholdersByOffset[2 * i - 2], + placeholdersByOffset[2 * i], + appendTo); + updatePlaceholderOffset( + placeholdersByOffset[2 * i + 1], + appendTo.length(), + offsetArray, + offsetArrayLength); + appendTo.append(*placeholderValues[placeholdersByOffset[2 * i + 1]]); + } + appendRange( + noPlaceholders, + placeholdersByOffset[2 * placeholderSize - 2], + noPlaceholders.length(), + appendTo); + return appendTo; +} + +int32_t Template::ensureCapacity(int32_t atLeast) { + if (atLeast <= placeholderCapacity) { + return atLeast; + } + // aim to double capacity each time + int32_t newCapacity = 2*atLeast - 2; + + // allocate new buffer + int32_t *newBuffer = (int32_t *) uprv_malloc(2 * newCapacity * sizeof(int32_t)); + if (newBuffer == NULL) { + return placeholderCapacity; + } + + // Copy contents of old buffer to new buffer + uprv_memcpy(newBuffer, placeholdersByOffset, 2 * placeholderSize * sizeof(int32_t)); + + // free old buffer + if (placeholdersByOffset != placeholderBuffer) { + uprv_free(placeholdersByOffset); + } + + // Use new buffer + placeholdersByOffset = newBuffer; + placeholderCapacity = newCapacity; + return atLeast; +} + +UBool Template::addPlaceholder(int32_t id, int32_t offset) { + if (ensureCapacity(placeholderSize + 1) < placeholderSize + 1) { + return FALSE; + } + ++placeholderSize; + placeholdersByOffset[2 * placeholderSize - 2] = offset; + placeholdersByOffset[2 * placeholderSize - 1] = id; + if (id >= placeholderCount) { + placeholderCount = id + 1; + } + return TRUE; +} + +U_NAMESPACE_END diff --git a/icu4c/source/common/template.h b/icu4c/source/common/template.h new file mode 100644 index 00000000000..4d5bfdb110b --- /dev/null +++ b/icu4c/source/common/template.h @@ -0,0 +1,150 @@ +/* +****************************************************************************** +* Copyright (C) 2014, International Business Machines +* Corporation and others. All Rights Reserved. +****************************************************************************** +* template.h +*/ + +#ifndef __TEMPLATE_H__ +#define __TEMPLATE_H__ + +#define EXPECTED_PLACEHOLDER_COUNT 3 + +#include "unicode/utypes.h" +#include "unicode/unistr.h" + +U_NAMESPACE_BEGIN + +/** + * Compiled version of a template such as "{1} was born in {0}". + *

+ * Using Template objects is both faster and safer than adhoc replacement. + * They are faster because they are precompiled; they are safer because they + * account for curly braces escaped by apostrophe ('). + * + * Placeholders are of the form \{[0-9]+\}. If a curly brace is preceded + * by a single quote, it becomes a curly brace instead of the start of a + * placeholder. Two single quotes resolve to one single quote. + *

+ * Concurrent calls only to const methods on a Template object are safe, + * but concurrent const and non-const method calls on a Template object + * are not safe and require synchronization. + *

+ * Example: + *

+ * Template template("{1} '{born} in {0}");
+ * UnicodeString england("england");
+ * UnicodeString paul("paul"); 
+ * UnicodeString *params[] = {&england, &paul};
+ * UnicodeString result;
+ * UErrorCode status = U_ZERO_ERROR;
+ * // Evaluates to: "paul {born} in england"
+ * template.evaluate(params, 2, result, status);
+ * 
+ */ +class U_COMMON_API Template : public UMemory { +public: + /** + * Default constructor + */ + Template(); + + /** + * Construct from a pattern. Will never fail if pattern has three or + * fewer placeholders in it. + */ + explicit Template(const UnicodeString& pattern); + + /** + * Copy constructor. + */ + Template(const Template& other); + + /** + * Assignment operator + */ + Template &operator=(const Template& other); + + /** + * Destructor. + */ + ~Template(); + + /** + * Compiles pattern and makes this object represent pattern. + * + * Returns TRUE on success; FALSE on failure. Will not fail if + * there are three or fewer placeholders in pattern. May fail with + * U_MEMORY_ALLOCATION_ERROR if there are more than three placeholders. + */ + UBool compile(const UnicodeString &pattern, UErrorCode &status); + + /** + * Returns (maxPlaceholderId + 1). For example + * Template("{0} {2}").getPlaceholderCount() evaluates to 3. + * Callers use this function to find out how many values are needed + * to evaluate this template. + */ + int32_t getPlaceholderCount() const { + return placeholderCount; + } + + /** + * Evaluates this template according to the given placeholder values. + * + * The caller retains ownership of all pointers. + * @param placeholderValues 1st one corresponds to {0}; 2nd to {1}; + * 3rd to {2} etc. + * @param placeholderValueCount the number of placeholder values + * must be at least large enough to provide values for all placeholders + * in this object. Otherwise status set to U_ILLEGAL_ARGUMENT_ERROR. + * @param appendTo resulting string appended here. + * @param status any error stored here. + */ + UnicodeString &evaluate( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &appendTo, + UErrorCode &status) const; + + /** + * Evaluates this template according to the given placeholder values. + * + * The caller retains ownership of all pointers. + * @param placeholderValues 1st one corresponds to {0}; 2nd to {1}; + * 3rd to {2} etc. + * @param placeholderValueCount the number of placeholder values + * must be at least large enough to provide values for all placeholders + * in this object. Otherwise status set to U_ILLEGAL_ARGUMENT_ERROR. + * @param appendTo resulting string appended here. + * @param offsetArray The offset of each placeholder value in appendTo + * stored here. The first value gets the offset of the value for {0}; + * the 2nd for {1}; the 3rd for {2} etc. -1 means that the corresponding + * placeholder does not exist in this object. If caller is not + * interested in offsets, it may pass NULL and 0 for the length. + * @param offsetArrayLength the size of offsetArray may be less than + * placeholderValueCount. + * @param status any error stored here. + */ + UnicodeString &evaluate( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &appendTo, + int32_t *offsetArray, + int32_t offsetArrayLength, + UErrorCode &status) const; +private: + UnicodeString noPlaceholders; + int32_t placeholderBuffer[EXPECTED_PLACEHOLDER_COUNT * 2]; + int32_t *placeholdersByOffset; + int32_t placeholderSize; + int32_t placeholderCapacity; + int32_t placeholderCount; + int32_t ensureCapacity(int32_t size); + UBool addPlaceholder(int32_t id, int32_t offset); +}; + +U_NAMESPACE_END + +#endif diff --git a/icu4c/source/common/unicode/listformatter.h b/icu4c/source/common/unicode/listformatter.h index b78420c8b33..1e5a0d8f22c 100644 --- a/icu4c/source/common/unicode/listformatter.h +++ b/icu4c/source/common/unicode/listformatter.h @@ -29,6 +29,9 @@ U_NAMESPACE_BEGIN /** @internal */ class Hashtable; +/** @internal */ +struct ListFormatInternal; + /* The following can't be #ifndef U_HIDE_INTERNAL_API, needed for other .h file declarations */ /** @internal */ struct ListFormatData : public UMemory { @@ -131,21 +134,31 @@ class U_COMMON_API ListFormatter : public UObject{ UnicodeString& appendTo, UErrorCode& errorCode) const; #ifndef U_HIDE_INTERNAL_API + /** + @internal for MeasureFormat + */ + UnicodeString& format( + const UnicodeString items[], + int32_t n_items, + UnicodeString& appendTo, + int32_t index, + int32_t &offset, + UErrorCode& errorCode) const; /** * @internal constructor made public for testing. */ - ListFormatter(const ListFormatData* listFormatterData); + ListFormatter(const ListFormatData &data); + ListFormatter(const ListFormatInternal* listFormatterInternal); #endif /* U_HIDE_INTERNAL_API */ private: static void initializeHash(UErrorCode& errorCode); - static const ListFormatData* getListFormatData(const Locale& locale, const char *style, UErrorCode& errorCode); + static const ListFormatInternal* getListFormatInternal(const Locale& locale, const char *style, UErrorCode& errorCode); ListFormatter(); - void addNewString(const UnicodeString& pattern, UnicodeString& originalString, - const UnicodeString& newString, UErrorCode& errorCode) const; - const ListFormatData* data; + ListFormatInternal* owned; + const ListFormatInternal* data; }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index d588692cede..63ddbc02a3f 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -1,6 +1,6 @@ #****************************************************************************** # -# Copyright (C) 1998-2013, International Business Machines +# Copyright (C) 1998-2014, International Business Machines # Corporation and others. All Rights Reserved. # #****************************************************************************** @@ -87,7 +87,7 @@ uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o uspoof_wsconf.o decfmtst.o s ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o locdspnm.o \ decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \ tzfmt.o compactdecimalformat.o gender.o region.o scriptset.o identifier_info.o \ -uregion.o reldatefmt.o +uregion.o reldatefmt.o quantityformatter.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp new file mode 100644 index 00000000000..f28e0bc95dc --- /dev/null +++ b/icu4c/source/i18n/quantityformatter.cpp @@ -0,0 +1,162 @@ +/* +****************************************************************************** +* Copyright (C) 2014, International Business Machines +* Corporation and others. All Rights Reserved. +****************************************************************************** +* quantityformatter.cpp +*/ +#include "quantityformatter.h" +#include "template.h" +#include "uassert.h" +#include "unicode/unistr.h" +#include "unicode/decimfmt.h" +#include "cstring.h" +#include "plurrule_impl.h" +#include "unicode/plurrule.h" +#include "charstr.h" +#include "unicode/fmtable.h" + +#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) + +U_NAMESPACE_BEGIN + +// other must always be first. +static const char * const gPluralForms[] = { + "other", "zero", "one", "two", "few", "many"}; + +static int32_t getPluralIndex(const char *pluralForm) { + int32_t len = LENGTHOF(gPluralForms); + for (int32_t i = 0; i < len; ++i) { + if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) { + return i; + } + } + return -1; +} + +QuantityFormatter::QuantityFormatter() { + for (int32_t i = 0; i < LENGTHOF(templates); ++i) { + templates[i] = NULL; + } +} + +QuantityFormatter::QuantityFormatter(const QuantityFormatter &other) { + for (int32_t i = 0; i < LENGTHOF(templates); ++i) { + if (other.templates[i] == NULL) { + templates[i] = NULL; + } else { + templates[i] = new Template(*other.templates[i]); + } + } +} + +QuantityFormatter &QuantityFormatter::operator=( + const QuantityFormatter& other) { + if (this == &other) { + return *this; + } + for (int32_t i = 0; i < LENGTHOF(templates); ++i) { + delete templates[i]; + if (other.templates[i] == NULL) { + templates[i] = NULL; + } else { + templates[i] = new Template(*other.templates[i]); + } + } + return *this; +} + +QuantityFormatter::~QuantityFormatter() { + for (int32_t i = 0; i < LENGTHOF(templates); ++i) { + delete templates[i]; + } +} + +void QuantityFormatter::reset() { + for (int32_t i = 0; i < LENGTHOF(templates); ++i) { + delete templates[i]; + templates[i] = NULL; + } +} + +UBool QuantityFormatter::add( + const char *variant, + const UnicodeString &rawPattern, + UErrorCode &status) { + if (U_FAILURE(status)) { + return FALSE; + } + int32_t pluralIndex = getPluralIndex(variant); + if (pluralIndex == -1) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return FALSE; + } + Template *newTemplate = new Template(rawPattern); + if (newTemplate == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return FALSE; + } + if (newTemplate->getPlaceholderCount() > 1) { + delete newTemplate; + status = U_ILLEGAL_ARGUMENT_ERROR; + return FALSE; + } + delete templates[pluralIndex]; + templates[pluralIndex] = newTemplate; + return TRUE; +} + +UnicodeString &QuantityFormatter::format( + const Formattable& quantity, + const NumberFormat &fmt, + const PluralRules &rules, + UnicodeString &appendTo, + UErrorCode &status) const { + if (U_FAILURE(status)) { + return appendTo; + } + UnicodeString count; + const DecimalFormat *decFmt = dynamic_cast(&fmt); + if (decFmt != NULL) { + FixedDecimal fd = decFmt->getFixedDecimal(quantity, status); + if (U_FAILURE(status)) { + return appendTo; + } + count = rules.select(fd); + } else { + if (quantity.getType() == Formattable::kDouble) { + count = rules.select(quantity.getDouble()); + } else if (quantity.getType() == Formattable::kLong) { + count = rules.select(quantity.getLong()); + } else if (quantity.getType() == Formattable::kInt64) { + count = rules.select((double) quantity.getInt64()); + } else { + status = U_ILLEGAL_ARGUMENT_ERROR; + return appendTo; + } + } + CharString buffer; + buffer.appendInvariantChars(count, status); + if (U_FAILURE(status)) { + return appendTo; + } + int32_t pluralIndex = getPluralIndex(buffer.data()); + if (pluralIndex == -1) { + pluralIndex = 0; + } + const Template *pattern = templates[pluralIndex]; + if (pattern == NULL) { + pattern = templates[0]; + } + if (pattern == NULL) { + status = U_INVALID_STATE_ERROR; + return appendTo; + } + UnicodeString formattedNumber; + FieldPosition pos(0); + fmt.format(quantity, formattedNumber, pos, status); + UnicodeString *params[] = {&formattedNumber}; + return pattern->evaluate(params, LENGTHOF(params), appendTo, status); +} + +U_NAMESPACE_END diff --git a/icu4c/source/i18n/quantityformatter.h b/icu4c/source/i18n/quantityformatter.h new file mode 100644 index 00000000000..3ef07df88c3 --- /dev/null +++ b/icu4c/source/i18n/quantityformatter.h @@ -0,0 +1,102 @@ +/* +****************************************************************************** +* Copyright (C) 2014, International Business Machines +* Corporation and others. All Rights Reserved. +****************************************************************************** +* quantityformatter.h +*/ + +#ifndef __QUANTITY_FORMATTER_H__ +#define __QUANTITY_FORMATTER_H__ + +#include "unicode/utypes.h" +#include "unicode/uobject.h" + +U_NAMESPACE_BEGIN + +class Template; +class UnicodeString; +class PluralRules; +class NumberFormat; +class Formattable; + +/** + * A plural aware template that is good for expressing a single quantity and + * a unit. + *

+ * First use the add() methods to add a pattern for each plural variant. + * There must be a pattern for the "other" variant. + * Then use the format() method. + *

+ * Concurrent calls only to const methods on a QuantityFormatter object are + * safe, but concurrent const and non-const method calls on a QuantityFormatter + * object are not safe and require synchronization. + * + */ +class U_COMMON_API QuantityFormatter : public UMemory { +// TODO(Travis Keep): Add test for copy constructor, assignment, and reset. +public: + /** + * Default constructor. + */ + QuantityFormatter(); + + /** + * Copy constructor. + */ + QuantityFormatter(const QuantityFormatter& other); + + /** + * Assignment operator + */ + QuantityFormatter &operator=(const QuantityFormatter& other); + + /** + * Destructor. + */ + ~QuantityFormatter(); + + /** + * Removes all variants from this object including the "other" variant. + */ + void reset(); + + /** + * Adds a plural variant. + * + * @param variant "zero", "one", "two", "few", "many", "other" + * @param rawPattern the pattern for the variant e.g "{0} meters" + * @param status any error returned here. + * @return TRUE on success; FALSE otherwise. + */ + UBool add( + const char *variant, + const UnicodeString &rawPattern, + UErrorCode &status); + + /** + * Formats a quantity with this object appending the result to appendTo. + * At least the "other" variant must be added to this object for this + * method to work. + * + * @param quantity the single quantity. + * @param fmt formats the quantity + * @param rules computes the plural variant to use. + * @param appendTo result appended here. + * @param status any error returned here. + * @return appendTo + */ + UnicodeString &format( + const Formattable &quantity, + const NumberFormat &fmt, + const PluralRules &rules, + UnicodeString &appendTo, + UErrorCode &status) const; + +private: + Template *templates[6]; +}; + +U_NAMESPACE_END + +#endif diff --git a/icu4c/source/i18n/reldatefmt.cpp b/icu4c/source/i18n/reldatefmt.cpp index c1341dfc211..8d8e6a9a6df 100644 --- a/icu4c/source/i18n/reldatefmt.cpp +++ b/icu4c/source/i18n/reldatefmt.cpp @@ -13,6 +13,7 @@ #if !UCONFIG_NO_FORMATTING #include "unicode/localpointer.h" +#include "quantityformatter.h" #include "unicode/plurrule.h" #include "unicode/msgfmt.h" #include "unicode/decimfmt.h" @@ -48,12 +49,6 @@ U_CDECL_END U_NAMESPACE_BEGIN -// other must always be first. -static const char * const gPluralForms[] = { - "other", "zero", "one", "two", "few", "many"}; - -static const UChar gPlaceholder[] = { 0x7b, 0x30, 0x7d}; /* {0} */ - class QualitativeUnits : public UMemory { public: QualitativeUnits() { } @@ -63,44 +58,10 @@ private: QualitativeUnits &operator=(const QualitativeUnits& other); }; -struct UnitPattern { - UnicodeString pattern; - int32_t offset; // Offset of "{0}". -1 means no {0}. - UBool valid; // True if initialize, false otherwise. - - UnitPattern() : pattern(), offset(0), valid(FALSE) { - } - - void set(const UnicodeString &patternStr) { - pattern = patternStr; - offset = patternStr.indexOf(gPlaceholder, LENGTHOF(gPlaceholder), 0); - valid = TRUE; - } - - UnicodeString& append( - double quantity, - const NumberFormat &nf, - UnicodeString &appendTo) const { - if (!valid) { - return appendTo; - } - if (offset == -1) { - appendTo.append(pattern); - return appendTo; - } - appendTo.append(pattern, 0, offset); - nf.format(quantity, appendTo); - appendTo.append(pattern, - offset + LENGTHOF(gPlaceholder), - 0x7fffffff); - return appendTo; - } -}; - class QuantitativeUnits : public UMemory { public: QuantitativeUnits() { } - UnitPattern data[UDAT_RELATIVE_UNIT_COUNT][2][LENGTHOF(gPluralForms)]; + QuantityFormatter data[UDAT_RELATIVE_UNIT_COUNT][2]; private: QuantitativeUnits(const QuantitativeUnits &other); QuantitativeUnits &operator=(const QuantitativeUnits& other); @@ -172,21 +133,6 @@ static UBool getString( return TRUE; } -static UBool getUnitPattern( - const UResourceBundle *resource, - UnitPattern &result, - UErrorCode &status) { - if (U_FAILURE(status)) { - return FALSE; - } - UnicodeString rawPattern; - if (!getString(resource, rawPattern, status)) { - return FALSE; - } - result.set(rawPattern); - return TRUE; -} - static UBool getStringByIndex( const UResourceBundle *resource, int32_t idx, @@ -236,16 +182,6 @@ static void addQualitativeUnit( qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_PLAIN] = unitName; } -static int32_t getPluralIndex(const char *pluralForm) { - int32_t len = LENGTHOF(gPluralForms); - for (int32_t i = 0; i < len; ++i) { - if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) { - return i; - } - } - return -1; -} - static void addTimeUnit( const UResourceBundle *resource, UDateRelativeUnit relativeUnit, @@ -262,15 +198,16 @@ static void addTimeUnit( if (U_FAILURE(status)) { return; } - int32_t pluralIndex = getPluralIndex( - ures_getKey(pluralBundle.getAlias())); - if (pluralIndex != -1) { - if (!getUnitPattern( - pluralBundle.getAlias(), - quantitativeUnits.data[relativeUnit][pastOrFuture][pluralIndex], - status)) { - return; - } + UnicodeString rawPattern; + if (!getString(pluralBundle.getAlias(), rawPattern, status)) { + return; + } + if (!quantitativeUnits.data[relativeUnit][pastOrFuture] + .add( + ures_getKey(pluralBundle.getAlias()), + rawPattern, + status)) { + return; } } } @@ -710,28 +647,13 @@ UnicodeString& RelativeDateTimeFormatter::format( status = U_ILLEGAL_ARGUMENT_ERROR; return appendTo; } - FixedDecimal dec(quantity); - const DecimalFormat *decFmt = dynamic_cast( - ptr->numberFormat.readOnly()); - if (decFmt != NULL) { - dec = decFmt->getFixedDecimal(quantity, status); - } - CharString buffer; - buffer.appendInvariantChars(ptr->pluralRules->select(dec), status); - if (U_FAILURE(status)) { - return appendTo; - } - - int32_t pluralIndex = getPluralIndex(buffer.data()); - if (pluralIndex == -1) { - pluralIndex = 0; - } int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0; - const UnitPattern *pattern = &ptr->quantitativeUnits->data[unit][bFuture][pluralIndex]; - if (!pattern->valid) { - pattern = &ptr->quantitativeUnits->data[unit][bFuture][0]; - } - return pattern->append(quantity, *ptr->numberFormat, appendTo); + return ptr->quantitativeUnits->data[unit][bFuture].format( + quantity, + *ptr->numberFormat, + *ptr->pluralRules, + appendTo, + status); } UnicodeString& RelativeDateTimeFormatter::format( diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index bc4e19108d0..be7fe6fd387 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -1,6 +1,6 @@ #****************************************************************************** # -# Copyright (C) 1999-2013, International Business Machines +# Copyright (C) 1999-2014, International Business Machines # Corporation and others. All Rights Reserved. # #****************************************************************************** @@ -56,7 +56,7 @@ uobjtest.o idnaref.o idnaconf.o nptrans.o punyref.o testidn.o testidna.o uts46te incaltst.o calcasts.o v32test.o uvectest.o textfile.o tokiter.o utxttest.o \ windttst.o winnmtst.o winutil.o csdetest.o tzrulets.o tzoffloc.o tzfmttst.o ssearch.o dtifmtts.o \ tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o regiontst.o \ -reldatefmttest.o lrucachetest.o +reldatefmttest.o lrucachetest.o templatetest.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/itutil.cpp b/icu4c/source/test/intltest/itutil.cpp index 537ea097684..742019bc073 100644 --- a/icu4c/source/test/intltest/itutil.cpp +++ b/icu4c/source/test/intltest/itutil.cpp @@ -1,6 +1,6 @@ /******************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2013, International Business Machines Corporation and + * Copyright (c) 1997-2014, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -34,6 +34,7 @@ static IntlTest *createLocalPointerTest(); extern IntlTest *createUCharsTrieTest(); static IntlTest *createEnumSetTest(); extern IntlTest *createLRUCacheTest(); +extern IntlTest *createTemplateTest(); #define CASE(id, test) case id: \ name = #test; \ @@ -104,6 +105,14 @@ void IntlTestUtilities::runIndexedTest( int32_t index, UBool exec, const char* & callTest(*test, par); } break; + case 21: + name = "TemplateTest"; + if (exec) { + logln("TestSuite TemplateTest---"); logln(); + LocalPointer test(createTemplateTest()); + callTest(*test, par); + } + break; default: name = ""; break; //needed to end loop } } diff --git a/icu4c/source/test/intltest/listformattertest.cpp b/icu4c/source/test/intltest/listformattertest.cpp index e62eef74954..1e0418361a6 100644 --- a/icu4c/source/test/intltest/listformattertest.cpp +++ b/icu4c/source/test/intltest/listformattertest.cpp @@ -1,7 +1,7 @@ /* ******************************************************************************* * -* Copyright (C) 2012-2013, International Business Machines +* Copyright (C) 2012-2014, International Business Machines * Corporation and others. All Rights Reserved. * ******************************************************************************* @@ -199,7 +199,7 @@ void ListFormatterTest::TestOutOfOrderPatterns() { ListFormatData data("{1} after {0}", "{1} after the first {0}", "{1} after {0}", "{1} in the last after {0}"); - ListFormatter formatter(&data); + ListFormatter formatter(data); UnicodeString input1[] = {one}; CheckFormatting(&formatter, input1, 1, results[0]); diff --git a/icu4c/source/test/intltest/templatetest.cpp b/icu4c/source/test/intltest/templatetest.cpp new file mode 100644 index 00000000000..6eb0e506dbc --- /dev/null +++ b/icu4c/source/test/intltest/templatetest.cpp @@ -0,0 +1,202 @@ +/* +******************************************************************************* +* Copyright (C) 2014, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +* +* File TEMPLATETEST.CPP +* +******************************************************************************** +*/ +#include "cstring.h" +#include "intltest.h" +#include "template.h" + +#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0])) + +class TemplateTest : public IntlTest { +public: + TemplateTest() { + } + void TestNoPlaceholders(); + void TestOnePlaceholder(); + void TestManyPlaceholders(); + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0); +private: +}; + +void TemplateTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) { + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(TestNoPlaceholders); + TESTCASE_AUTO(TestOnePlaceholder); + TESTCASE_AUTO(TestManyPlaceholders); + TESTCASE_AUTO_END; +} + +void TemplateTest::TestNoPlaceholders() { + UErrorCode status = U_ZERO_ERROR; + Template t("This doesn''t have templates '{0}"); + assertEquals("PlaceholderCount", 0, t.getPlaceholderCount()); + UnicodeString appendTo; + assertEquals( + "Evaluate", + "This doesn't have templates {0}", + t.evaluate( + NULL, + 0, + appendTo, + status)); + appendTo.remove(); + t.compile("This has {} bad {012d placeholders", status); + assertEquals("PlaceholderCount", 0, t.getPlaceholderCount()); + assertEquals( + "Evaluate", + "This has {} bad {012d placeholders", + t.evaluate( + NULL, + 0, + appendTo, + status)); + appendTo.remove(); + assertSuccess("Status", status); +} + +void TemplateTest::TestOnePlaceholder() { + UErrorCode status = U_ZERO_ERROR; + Template t; + t.compile("{0} meter", status); + assertEquals("PlaceholderCount", 1, t.getPlaceholderCount()); + UnicodeString one("1"); + UnicodeString *params[] = {&one}; + UnicodeString appendTo; + assertEquals( + "Evaluate", + "1 meter", + t.evaluate( + params, + LENGTHOF(params), + appendTo, + status)); + appendTo.remove(); + assertSuccess("Status", status); + + // assignment + Template s; + s = t; + assertEquals( + "Assignment", + "1 meter", + s.evaluate( + params, + LENGTHOF(params), + appendTo, + status)); + appendTo.remove(); + + // Copy constructor + Template r(t); + assertEquals( + "Copy constructor", + "1 meter", + r.evaluate( + params, + LENGTHOF(params), + appendTo, + status)); + appendTo.remove(); + assertSuccess("Status", status); +} + +void TemplateTest::TestManyPlaceholders() { + UErrorCode status = U_ZERO_ERROR; + Template t; + t.compile( + "Templates {2}{1}{5} and {4} are out of order.", status); + assertEquals("PlaceholderCount", 6, t.getPlaceholderCount()); + UnicodeString values[] = { + "freddy", "tommy", "frog", "billy", "leg", "{0}"}; + UnicodeString *params[] = { + &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]}; + int32_t offsets[6]; + int32_t expectedOffsets[6] = {-1, 14, 10, -1, 27, 19}; + UnicodeString appendTo; + assertEquals( + "Evaluate", + "Templates frogtommy{0} and leg are out of order.", + t.evaluate( + params, + LENGTHOF(params), + appendTo, + offsets, + LENGTHOF(offsets), + status)); + appendTo.remove(); + assertSuccess("Status", status); + for (int32_t i = 0; i < LENGTHOF(expectedOffsets); ++i) { + if (expectedOffsets[i] != offsets[i]) { + errln("Expected %d, got %d", expectedOffsets[i], offsets[i]); + } + } + t.evaluate( + params, + LENGTHOF(params) - 1, + appendTo, + offsets, + LENGTHOF(offsets), + status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + status = U_ZERO_ERROR; + offsets[LENGTHOF(offsets) - 1] = 289; + t.evaluate( + params, + LENGTHOF(params), + appendTo, + offsets, + LENGTHOF(offsets) - 1, + status); + appendTo.remove(); + assertEquals("Offsets buffer length", 289, offsets[LENGTHOF(offsets) - 1]); + + // Test assignment + Template s; + s = t; + assertEquals( + "Assignment", + "Templates frogtommy{0} and leg are out of order.", + s.evaluate( + params, + LENGTHOF(params), + appendTo, + status)); + appendTo.remove(); + + // Copy constructor + Template r(t); + assertEquals( + "Assignment", + "Templates frogtommy{0} and leg are out of order.", + r.evaluate( + params, + LENGTHOF(params), + appendTo, + status)); + appendTo.remove(); + r.compile("{0} meter", status); + assertEquals("PlaceholderCount", 1, r.getPlaceholderCount()); + assertEquals( + "Assignment", + "freddy meter", + r.evaluate( + params, + 1, + appendTo, + status)); + appendTo.remove(); + assertSuccess("Status", status); +} + +extern IntlTest *createTemplateTest() { + return new TemplateTest(); +} -- 2.40.0