ICU-10646 Introduce Template class and change ListFormatter to use it. Introduce QuantityFormatter class and change RelativeDateTimeFormatter to use it.

X-SVN-Rev: 35018
This commit is contained in:
Travis Keep 2014-01-29 05:08:55 +00:00
parent 1cf0f06b01
commit d4ec281c99
14 changed files with 1176 additions and 190 deletions

5
.gitattributes vendored
View file

@ -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

View file

@ -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

View file

@ -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<ListFormatData *>(obj);
uprv_deleteListFormatInternal(void *obj) {
delete static_cast<ListFormatInternal *>(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);
static ListFormatInternal* loadListFormatInternal(
const Locale& locale,
const char* style,
UErrorCode& errorCode);
ListFormatter::ListFormatter(const ListFormatter& other) : data(other.data) {
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<ListFormatData*>(listPatternHash->get(key));
result = static_cast<ListFormatInternal*>(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<ListFormatData*>(listPatternHash->get(key));
ListFormatInternal* temp = static_cast<ListFormatInternal*>(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,14 +239,72 @@ 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() {}
ListFormatter::ListFormatter(const ListFormatInternal* listFormatterInternal) : owned(NULL), data(listFormatterInternal) {
}
UnicodeString& ListFormatter::format(const UnicodeString items[], int32_t nItems,
UnicodeString& appendTo, UErrorCode& errorCode) const {
ListFormatter::~ListFormatter() {
delete owned;
}
/**
* Joins first and second using the pattern pat.
*/
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;
}
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;
}
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];
}
}
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);
}
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;
}
@ -200,67 +313,72 @@ UnicodeString& ListFormatter::format(const UnicodeString items[], int32_t nItems
return appendTo;
}
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 (nItems <= 0) {
return appendTo;
}
if (nItems == 1) {
if (index == 0) {
offset = appendTo.length();
}
if (U_SUCCESS(errorCode)) {
appendTo += newString;
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;
}
/**
* Joins originalString and nextString using the pattern pat and puts the result in
* originalString.
*/
void ListFormatter::addNewString(const UnicodeString& pat, UnicodeString& originalString,
const UnicodeString& nextString, UErrorCode& errorCode) const {
if (U_FAILURE(errorCode)) {
return;
}
int32_t p0Offset = pat.indexOf(FIRST_PARAMETER, 3, 0);
if (p0Offset < 0) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
int32_t p1Offset = pat.indexOf(SECOND_PARAMETER, 3, 0);
if (p1Offset < 0) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
int32_t i, j;
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 result = UnicodeString(pat, 0, i) + *firstString;
result += UnicodeString(pat, i+3, j-i-3);
result += *secondString;
result += UnicodeString(pat, j+3);
originalString = result;
}
U_NAMESPACE_END

View file

@ -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

View file

@ -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}".
* <p>
* 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.
* <p>
* 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.
* <p>
* Example:
* <pre>
* 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);
* </pre>
*/
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
* <code>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

View file

@ -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

View file

@ -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

View file

@ -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<const DecimalFormat *>(&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

View file

@ -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.
* <p>
* 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.
* <p>
* 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

View file

@ -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<const DecimalFormat *>(
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(

View file

@ -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)

View file

@ -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<IntlTest> test(createTemplateTest());
callTest(*test, par);
}
break;
default: name = ""; break; //needed to end loop
}
}

View file

@ -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]);

View file

@ -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();
}