blob: c1ec10077c6767c05f77b74b2ed93fd0a4c18813 [file] [log] [blame]
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/********************************************************************
* COPYRIGHT:
* Copyright (c) 1997-2015, International Business Machines Corporation and
* others. All Rights Reserved.
********************************************************************
*
* File MSGFMT.CPP
*
* Modification History:
*
* Date Name Description
* 02/19/97 aliu Converted from java.
* 03/20/97 helena Finished first cut of implementation.
* 04/10/97 aliu Made to work on AIX. Added stoi to replace wtoi.
* 06/11/97 helena Fixed addPattern to take the pattern correctly.
* 06/17/97 helena Fixed the getPattern to return the correct pattern.
* 07/09/97 helena Made ParsePosition into a class.
* 02/22/99 stephen Removed character literals for EBCDIC safety
* 11/01/09 kirtig Added SelectFormat
********************************************************************/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#if defined(STARBOARD)
#include "starboard/client_porting/poem/assert_poem.h"
#include "starboard/client_porting/poem/string_poem.h"
#endif // defined(STARBOARD)
#include "unicode/appendable.h"
#include "unicode/choicfmt.h"
#include "unicode/datefmt.h"
#include "unicode/decimfmt.h"
#include "unicode/localpointer.h"
#include "unicode/msgfmt.h"
#include "unicode/numberformatter.h"
#include "unicode/plurfmt.h"
#include "unicode/rbnf.h"
#include "unicode/selfmt.h"
#include "unicode/smpdtfmt.h"
#include "unicode/umsg.h"
#include "unicode/ustring.h"
#include "cmemory.h"
#include "patternprops.h"
#include "messageimpl.h"
#include "msgfmt_impl.h"
#include "plurrule_impl.h"
#include "uassert.h"
#include "uelement.h"
#include "uhash.h"
#include "ustrfmt.h"
#include "util.h"
#include "uvector.h"
#include "number_decimalquantity.h"
// *****************************************************************************
// class MessageFormat
// *****************************************************************************
#define SINGLE_QUOTE ((UChar)0x0027)
#define COMMA ((UChar)0x002C)
#define LEFT_CURLY_BRACE ((UChar)0x007B)
#define RIGHT_CURLY_BRACE ((UChar)0x007D)
//---------------------------------------
// static data
static const UChar ID_NUMBER[] = {
0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0 /* "number" */
};
static const UChar ID_DATE[] = {
0x64, 0x61, 0x74, 0x65, 0 /* "date" */
};
static const UChar ID_TIME[] = {
0x74, 0x69, 0x6D, 0x65, 0 /* "time" */
};
static const UChar ID_SPELLOUT[] = {
0x73, 0x70, 0x65, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0 /* "spellout" */
};
static const UChar ID_ORDINAL[] = {
0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0 /* "ordinal" */
};
static const UChar ID_DURATION[] = {
0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0 /* "duration" */
};
// MessageFormat Type List Number, Date, Time or Choice
static const UChar * const TYPE_IDS[] = {
ID_NUMBER,
ID_DATE,
ID_TIME,
ID_SPELLOUT,
ID_ORDINAL,
ID_DURATION,
NULL,
};
static const UChar ID_EMPTY[] = {
0 /* empty string, used for default so that null can mark end of list */
};
static const UChar ID_CURRENCY[] = {
0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x63, 0x79, 0 /* "currency" */
};
static const UChar ID_PERCENT[] = {
0x70, 0x65, 0x72, 0x63, 0x65, 0x6E, 0x74, 0 /* "percent" */
};
static const UChar ID_INTEGER[] = {
0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0 /* "integer" */
};
// NumberFormat modifier list, default, currency, percent or integer
static const UChar * const NUMBER_STYLE_IDS[] = {
ID_EMPTY,
ID_CURRENCY,
ID_PERCENT,
ID_INTEGER,
NULL,
};
static const UChar ID_SHORT[] = {
0x73, 0x68, 0x6F, 0x72, 0x74, 0 /* "short" */
};
static const UChar ID_MEDIUM[] = {
0x6D, 0x65, 0x64, 0x69, 0x75, 0x6D, 0 /* "medium" */
};
static const UChar ID_LONG[] = {
0x6C, 0x6F, 0x6E, 0x67, 0 /* "long" */
};
static const UChar ID_FULL[] = {
0x66, 0x75, 0x6C, 0x6C, 0 /* "full" */
};
// DateFormat modifier list, default, short, medium, long or full
static const UChar * const DATE_STYLE_IDS[] = {
ID_EMPTY,
ID_SHORT,
ID_MEDIUM,
ID_LONG,
ID_FULL,
NULL,
};
static const icu::DateFormat::EStyle DATE_STYLES[] = {
icu::DateFormat::kDefault,
icu::DateFormat::kShort,
icu::DateFormat::kMedium,
icu::DateFormat::kLong,
icu::DateFormat::kFull,
};
static const int32_t DEFAULT_INITIAL_CAPACITY = 10;
static const UChar NULL_STRING[] = {
0x6E, 0x75, 0x6C, 0x6C, 0 // "null"
};
static const UChar OTHER_STRING[] = {
0x6F, 0x74, 0x68, 0x65, 0x72, 0 // "other"
};
U_CDECL_BEGIN
static UBool U_CALLCONV equalFormatsForHash(const UHashTok key1,
const UHashTok key2) {
return icu::MessageFormat::equalFormats(key1.pointer, key2.pointer);
}
U_CDECL_END
U_NAMESPACE_BEGIN
// -------------------------------------
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MessageFormat)
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(FormatNameEnumeration)
//--------------------------------------------------------------------
/**
* Convert an integer value to a string and append the result to
* the given UnicodeString.
*/
static UnicodeString& itos(int32_t i, UnicodeString& appendTo) {
UChar temp[16];
uprv_itou(temp,16,i,10,0); // 10 == radix
appendTo.append(temp, -1);
return appendTo;
}
// AppendableWrapper: encapsulates the result of formatting, keeping track
// of the string and its length.
class AppendableWrapper : public UMemory {
public:
AppendableWrapper(Appendable& appendable) : app(appendable), len(0) {
}
void append(const UnicodeString& s) {
app.appendString(s.getBuffer(), s.length());
len += s.length();
}
void append(const UChar* s, const int32_t sLength) {
app.appendString(s, sLength);
len += sLength;
}
void append(const UnicodeString& s, int32_t start, int32_t length) {
append(s.tempSubString(start, length));
}
void formatAndAppend(const Format* formatter, const Formattable& arg, UErrorCode& ec) {
UnicodeString s;
formatter->format(arg, s, ec);
if (U_SUCCESS(ec)) {
append(s);
}
}
void formatAndAppend(const Format* formatter, const Formattable& arg,
const UnicodeString &argString, UErrorCode& ec) {
if (!argString.isEmpty()) {
if (U_SUCCESS(ec)) {
append(argString);
}
} else {
formatAndAppend(formatter, arg, ec);
}
}
int32_t length() {
return len;
}
private:
Appendable& app;
int32_t len;
};
// -------------------------------------
// Creates a MessageFormat instance based on the pattern.
MessageFormat::MessageFormat(const UnicodeString& pattern,
UErrorCode& success)
: fLocale(Locale::getDefault()), // Uses the default locale
msgPattern(success),
formatAliases(NULL),
formatAliasesCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
hasArgTypeConflicts(FALSE),
defaultNumberFormat(NULL),
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, success);
}
MessageFormat::MessageFormat(const UnicodeString& pattern,
const Locale& newLocale,
UErrorCode& success)
: fLocale(newLocale),
msgPattern(success),
formatAliases(NULL),
formatAliasesCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
hasArgTypeConflicts(FALSE),
defaultNumberFormat(NULL),
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, success);
}
MessageFormat::MessageFormat(const UnicodeString& pattern,
const Locale& newLocale,
UParseError& parseError,
UErrorCode& success)
: fLocale(newLocale),
msgPattern(success),
formatAliases(NULL),
formatAliasesCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
hasArgTypeConflicts(FALSE),
defaultNumberFormat(NULL),
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
setLocaleIDs(fLocale.getName(), fLocale.getName());
applyPattern(pattern, parseError, success);
}
MessageFormat::MessageFormat(const MessageFormat& that)
:
Format(that),
fLocale(that.fLocale),
msgPattern(that.msgPattern),
formatAliases(NULL),
formatAliasesCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
hasArgTypeConflicts(that.hasArgTypeConflicts),
defaultNumberFormat(NULL),
defaultDateFormat(NULL),
cachedFormatters(NULL),
customFormatArgStarts(NULL),
pluralProvider(*this, UPLURAL_TYPE_CARDINAL),
ordinalProvider(*this, UPLURAL_TYPE_ORDINAL)
{
// This will take care of creating the hash tables (since they are NULL).
UErrorCode ec = U_ZERO_ERROR;
copyObjects(that, ec);
if (U_FAILURE(ec)) {
resetPattern();
}
}
MessageFormat::~MessageFormat()
{
uhash_close(cachedFormatters);
uhash_close(customFormatArgStarts);
uprv_free(argTypes);
uprv_free(formatAliases);
delete defaultNumberFormat;
delete defaultDateFormat;
}
//--------------------------------------------------------------------
// Variable-size array management
/**
* Allocate argTypes[] to at least the given capacity and return
* TRUE if successful. If not, leave argTypes[] unchanged.
*
* If argTypes is NULL, allocate it. If it is not NULL, enlarge it
* if necessary to be at least as large as specified.
*/
UBool MessageFormat::allocateArgTypes(int32_t capacity, UErrorCode& status) {
if (U_FAILURE(status)) {
return FALSE;
}
if (argTypeCapacity >= capacity) {
return TRUE;
}
if (capacity < DEFAULT_INITIAL_CAPACITY) {
capacity = DEFAULT_INITIAL_CAPACITY;
} else if (capacity < 2*argTypeCapacity) {
capacity = 2*argTypeCapacity;
}
Formattable::Type* a = (Formattable::Type*)
uprv_realloc(argTypes, sizeof(*argTypes) * capacity);
if (a == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return FALSE;
}
argTypes = a;
argTypeCapacity = capacity;
return TRUE;
}
// -------------------------------------
// assignment operator
const MessageFormat&
MessageFormat::operator=(const MessageFormat& that)
{
if (this != &that) {
// Calls the super class for assignment first.
Format::operator=(that);
setLocale(that.fLocale);
msgPattern = that.msgPattern;
hasArgTypeConflicts = that.hasArgTypeConflicts;
UErrorCode ec = U_ZERO_ERROR;
copyObjects(that, ec);
if (U_FAILURE(ec)) {
resetPattern();
}
}
return *this;
}
UBool
MessageFormat::operator==(const Format& rhs) const
{
if (this == &rhs) return TRUE;
MessageFormat& that = (MessageFormat&)rhs;
// Check class ID before checking MessageFormat members
if (!Format::operator==(rhs) ||
msgPattern != that.msgPattern ||
fLocale != that.fLocale) {
return FALSE;
}
// Compare hashtables.
if ((customFormatArgStarts == NULL) != (that.customFormatArgStarts == NULL)) {
return FALSE;
}
if (customFormatArgStarts == NULL) {
return TRUE;
}
UErrorCode ec = U_ZERO_ERROR;
const int32_t count = uhash_count(customFormatArgStarts);
const int32_t rhs_count = uhash_count(that.customFormatArgStarts);
if (count != rhs_count) {
return FALSE;
}
int32_t idx = 0, rhs_idx = 0, pos = UHASH_FIRST, rhs_pos = UHASH_FIRST;
for (; idx < count && rhs_idx < rhs_count && U_SUCCESS(ec); ++idx, ++rhs_idx) {
const UHashElement* cur = uhash_nextElement(customFormatArgStarts, &pos);
const UHashElement* rhs_cur = uhash_nextElement(that.customFormatArgStarts, &rhs_pos);
if (cur->key.integer != rhs_cur->key.integer) {
return FALSE;
}
const Format* format = (const Format*)uhash_iget(cachedFormatters, cur->key.integer);
const Format* rhs_format = (const Format*)uhash_iget(that.cachedFormatters, rhs_cur->key.integer);
if (*format != *rhs_format) {
return FALSE;
}
}
return TRUE;
}
// -------------------------------------
// Creates a copy of this MessageFormat, the caller owns the copy.
MessageFormat*
MessageFormat::clone() const
{
return new MessageFormat(*this);
}
// -------------------------------------
// Sets the locale of this MessageFormat object to theLocale.
void
MessageFormat::setLocale(const Locale& theLocale)
{
if (fLocale != theLocale) {
delete defaultNumberFormat;
defaultNumberFormat = NULL;
delete defaultDateFormat;
defaultDateFormat = NULL;
fLocale = theLocale;
setLocaleIDs(fLocale.getName(), fLocale.getName());
pluralProvider.reset();
ordinalProvider.reset();
}
}
// -------------------------------------
// Gets the locale of this MessageFormat object.
const Locale&
MessageFormat::getLocale() const
{
return fLocale;
}
void
MessageFormat::applyPattern(const UnicodeString& newPattern,
UErrorCode& status)
{
UParseError parseError;
applyPattern(newPattern,parseError,status);
}
// -------------------------------------
// Applies the new pattern and returns an error if the pattern
// is not correct.
void
MessageFormat::applyPattern(const UnicodeString& pattern,
UParseError& parseError,
UErrorCode& ec)
{
if(U_FAILURE(ec)) {
return;
}
msgPattern.parse(pattern, &parseError, ec);
cacheExplicitFormats(ec);
if (U_FAILURE(ec)) {
resetPattern();
}
}
void MessageFormat::resetPattern() {
msgPattern.clear();
uhash_close(cachedFormatters);
cachedFormatters = NULL;
uhash_close(customFormatArgStarts);
customFormatArgStarts = NULL;
argTypeCount = 0;
hasArgTypeConflicts = FALSE;
}
void
MessageFormat::applyPattern(const UnicodeString& pattern,
UMessagePatternApostropheMode aposMode,
UParseError* parseError,
UErrorCode& status) {
if (aposMode != msgPattern.getApostropheMode()) {
msgPattern.clearPatternAndSetApostropheMode(aposMode);
}
applyPattern(pattern, *parseError, status);
}
// -------------------------------------
// Converts this MessageFormat instance to a pattern.
UnicodeString&
MessageFormat::toPattern(UnicodeString& appendTo) const {
if ((customFormatArgStarts != NULL && 0 != uhash_count(customFormatArgStarts)) ||
0 == msgPattern.countParts()
) {
appendTo.setToBogus();
return appendTo;
}
return appendTo.append(msgPattern.getPatternString());
}
int32_t MessageFormat::nextTopLevelArgStart(int32_t partIndex) const {
if (partIndex != 0) {
partIndex = msgPattern.getLimitPartIndex(partIndex);
}
for (;;) {
UMessagePatternPartType type = msgPattern.getPartType(++partIndex);
if (type == UMSGPAT_PART_TYPE_ARG_START) {
return partIndex;
}
if (type == UMSGPAT_PART_TYPE_MSG_LIMIT) {
return -1;
}
}
}
void MessageFormat::setArgStartFormat(int32_t argStart,
Format* formatter,
UErrorCode& status) {
if (U_FAILURE(status)) {
delete formatter;
return;
}
if (cachedFormatters == NULL) {
cachedFormatters=uhash_open(uhash_hashLong, uhash_compareLong,
equalFormatsForHash, &status);
if (U_FAILURE(status)) {
delete formatter;
return;
}
uhash_setValueDeleter(cachedFormatters, uprv_deleteUObject);
}
if (formatter == NULL) {
formatter = new DummyFormat();
}
uhash_iput(cachedFormatters, argStart, formatter, &status);
}
UBool MessageFormat::argNameMatches(int32_t partIndex, const UnicodeString& argName, int32_t argNumber) {
const MessagePattern::Part& part = msgPattern.getPart(partIndex);
return part.getType() == UMSGPAT_PART_TYPE_ARG_NAME ?
msgPattern.partSubstringMatches(part, argName) :
part.getValue() == argNumber; // ARG_NUMBER
}
// Sets a custom formatter for a MessagePattern ARG_START part index.
// "Custom" formatters are provided by the user via setFormat() or similar APIs.
void MessageFormat::setCustomArgStartFormat(int32_t argStart,
Format* formatter,
UErrorCode& status) {
setArgStartFormat(argStart, formatter, status);
if (customFormatArgStarts == NULL) {
customFormatArgStarts=uhash_open(uhash_hashLong, uhash_compareLong,
NULL, &status);
}
uhash_iputi(customFormatArgStarts, argStart, 1, &status);
}
Format* MessageFormat::getCachedFormatter(int32_t argumentNumber) const {
if (cachedFormatters == NULL) {
return NULL;
}
void* ptr = uhash_iget(cachedFormatters, argumentNumber);
if (ptr != NULL && dynamic_cast<DummyFormat*>((Format*)ptr) == NULL) {
return (Format*) ptr;
} else {
// Not cached, or a DummyFormat representing setFormat(NULL).
return NULL;
}
}
// -------------------------------------
// Adopts the new formats array and updates the array count.
// This MessageFormat instance owns the new formats.
void
MessageFormat::adoptFormats(Format** newFormats,
int32_t count) {
if (newFormats == NULL || count < 0) {
return;
}
// Throw away any cached formatters.
if (cachedFormatters != NULL) {
uhash_removeAll(cachedFormatters);
}
if (customFormatArgStarts != NULL) {
uhash_removeAll(customFormatArgStarts);
}
int32_t formatNumber = 0;
UErrorCode status = U_ZERO_ERROR;
for (int32_t partIndex = 0;
formatNumber < count && U_SUCCESS(status) &&
(partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
setCustomArgStartFormat(partIndex, newFormats[formatNumber], status);
++formatNumber;
}
// Delete those that didn't get used (if any).
for (; formatNumber < count; ++formatNumber) {
delete newFormats[formatNumber];
}
}
// -------------------------------------
// Sets the new formats array and updates the array count.
// This MessageFormat instance maks a copy of the new formats.
void
MessageFormat::setFormats(const Format** newFormats,
int32_t count) {
if (newFormats == NULL || count < 0) {
return;
}
// Throw away any cached formatters.
if (cachedFormatters != NULL) {
uhash_removeAll(cachedFormatters);
}
if (customFormatArgStarts != NULL) {
uhash_removeAll(customFormatArgStarts);
}
UErrorCode status = U_ZERO_ERROR;
int32_t formatNumber = 0;
for (int32_t partIndex = 0;
formatNumber < count && U_SUCCESS(status) && (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
Format* newFormat = NULL;
if (newFormats[formatNumber] != NULL) {
newFormat = newFormats[formatNumber]->clone();
if (newFormat == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
setCustomArgStartFormat(partIndex, newFormat, status);
++formatNumber;
}
if (U_FAILURE(status)) {
resetPattern();
}
}
// -------------------------------------
// Adopt a single format by format number.
// Do nothing if the format number is not less than the array count.
void
MessageFormat::adoptFormat(int32_t n, Format *newFormat) {
LocalPointer<Format> p(newFormat);
if (n >= 0) {
int32_t formatNumber = 0;
for (int32_t partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (n == formatNumber) {
UErrorCode status = U_ZERO_ERROR;
setCustomArgStartFormat(partIndex, p.orphan(), status);
return;
}
++formatNumber;
}
}
}
// -------------------------------------
// Adopt a single format by format name.
// Do nothing if there is no match of formatName.
void
MessageFormat::adoptFormat(const UnicodeString& formatName,
Format* formatToAdopt,
UErrorCode& status) {
LocalPointer<Format> p(formatToAdopt);
if (U_FAILURE(status)) {
return;
}
int32_t argNumber = MessagePattern::validateArgumentName(formatName);
if (argNumber < UMSGPAT_ARG_NAME_NOT_NUMBER) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
for (int32_t partIndex = 0;
(partIndex = nextTopLevelArgStart(partIndex)) >= 0 && U_SUCCESS(status);
) {
if (argNameMatches(partIndex + 1, formatName, argNumber)) {
Format* f;
if (p.isValid()) {
f = p.orphan();
} else if (formatToAdopt == NULL) {
f = NULL;
} else {
f = formatToAdopt->clone();
if (f == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
setCustomArgStartFormat(partIndex, f, status);
}
}
}
// -------------------------------------
// Set a single format.
// Do nothing if the variable is not less than the array count.
void
MessageFormat::setFormat(int32_t n, const Format& newFormat) {
if (n >= 0) {
int32_t formatNumber = 0;
for (int32_t partIndex = 0;
(partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (n == formatNumber) {
Format* new_format = newFormat.clone();
if (new_format) {
UErrorCode status = U_ZERO_ERROR;
setCustomArgStartFormat(partIndex, new_format, status);
}
return;
}
++formatNumber;
}
}
}
// -------------------------------------
// Get a single format by format name.
// Do nothing if the variable is not less than the array count.
Format *
MessageFormat::getFormat(const UnicodeString& formatName, UErrorCode& status) {
if (U_FAILURE(status) || cachedFormatters == NULL) return NULL;
int32_t argNumber = MessagePattern::validateArgumentName(formatName);
if (argNumber < UMSGPAT_ARG_NAME_NOT_NUMBER) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return NULL;
}
for (int32_t partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (argNameMatches(partIndex + 1, formatName, argNumber)) {
return getCachedFormatter(partIndex);
}
}
return NULL;
}
// -------------------------------------
// Set a single format by format name
// Do nothing if the variable is not less than the array count.
void
MessageFormat::setFormat(const UnicodeString& formatName,
const Format& newFormat,
UErrorCode& status) {
if (U_FAILURE(status)) return;
int32_t argNumber = MessagePattern::validateArgumentName(formatName);
if (argNumber < UMSGPAT_ARG_NAME_NOT_NUMBER) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
for (int32_t partIndex = 0;
(partIndex = nextTopLevelArgStart(partIndex)) >= 0 && U_SUCCESS(status);
) {
if (argNameMatches(partIndex + 1, formatName, argNumber)) {
Format* new_format = newFormat.clone();
if (new_format == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
setCustomArgStartFormat(partIndex, new_format, status);
}
}
}
// -------------------------------------
// Gets the format array.
const Format**
MessageFormat::getFormats(int32_t& cnt) const
{
// This old API returns an array (which we hold) of Format*
// pointers. The array is valid up to the next call to any
// method on this object. We construct and resize an array
// on demand that contains aliases to the subformats[i].format
// pointers.
// Get total required capacity first (it's refreshed on each call).
int32_t totalCapacity = 0;
for (int32_t partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0; ++totalCapacity) {}
MessageFormat* t = const_cast<MessageFormat*> (this);
cnt = 0;
if (formatAliases == nullptr) {
t->formatAliasesCapacity = totalCapacity;
Format** a = (Format**)
uprv_malloc(sizeof(Format*) * formatAliasesCapacity);
if (a == nullptr) {
t->formatAliasesCapacity = 0;
return nullptr;
}
t->formatAliases = a;
} else if (totalCapacity > formatAliasesCapacity) {
Format** a = (Format**)
uprv_realloc(formatAliases, sizeof(Format*) * totalCapacity);
if (a == nullptr) {
t->formatAliasesCapacity = 0;
return nullptr;
}
t->formatAliases = a;
t->formatAliasesCapacity = totalCapacity;
}
for (int32_t partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
t->formatAliases[cnt++] = getCachedFormatter(partIndex);
}
return (const Format**)formatAliases;
}
UnicodeString MessageFormat::getArgName(int32_t partIndex) {
const MessagePattern::Part& part = msgPattern.getPart(partIndex);
return msgPattern.getSubstring(part);
}
StringEnumeration*
MessageFormat::getFormatNames(UErrorCode& status) {
if (U_FAILURE(status)) return NULL;
UVector *fFormatNames = new UVector(status);
if (U_FAILURE(status)) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
fFormatNames->setDeleter(uprv_deleteUObject);
for (int32_t partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
fFormatNames->addElement(new UnicodeString(getArgName(partIndex + 1)), status);
}
StringEnumeration* nameEnumerator = new FormatNameEnumeration(fFormatNames, status);
return nameEnumerator;
}
// -------------------------------------
// Formats the source Formattable array and copy into the result buffer.
// Ignore the FieldPosition result for error checking.
UnicodeString&
MessageFormat::format(const Formattable* source,
int32_t cnt,
UnicodeString& appendTo,
FieldPosition& ignore,
UErrorCode& success) const
{
return format(source, NULL, cnt, appendTo, &ignore, success);
}
// -------------------------------------
// Internally creates a MessageFormat instance based on the
// pattern and formats the arguments Formattable array and
// copy into the appendTo buffer.
UnicodeString&
MessageFormat::format( const UnicodeString& pattern,
const Formattable* arguments,
int32_t cnt,
UnicodeString& appendTo,
UErrorCode& success)
{
MessageFormat temp(pattern, success);
return temp.format(arguments, NULL, cnt, appendTo, NULL, success);
}
// -------------------------------------
// Formats the source Formattable object and copy into the
// appendTo buffer. The Formattable object must be an array
// of Formattable instances, returns error otherwise.
UnicodeString&
MessageFormat::format(const Formattable& source,
UnicodeString& appendTo,
FieldPosition& ignore,
UErrorCode& success) const
{
if (U_FAILURE(success))
return appendTo;
if (source.getType() != Formattable::kArray) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
int32_t cnt;
const Formattable* tmpPtr = source.getArray(cnt);
return format(tmpPtr, NULL, cnt, appendTo, &ignore, success);
}
UnicodeString&
MessageFormat::format(const UnicodeString* argumentNames,
const Formattable* arguments,
int32_t count,
UnicodeString& appendTo,
UErrorCode& success) const {
return format(arguments, argumentNames, count, appendTo, NULL, success);
}
// Does linear search to find the match for an ArgName.
const Formattable* MessageFormat::getArgFromListByName(const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt, UnicodeString& name) const {
for (int32_t i = 0; i < cnt; ++i) {
if (0 == argumentNames[i].compare(name)) {
return arguments + i;
}
}
return NULL;
}
UnicodeString&
MessageFormat::format(const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
UnicodeString& appendTo,
FieldPosition* pos,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
UnicodeStringAppendable usapp(appendTo);
AppendableWrapper app(usapp);
format(0, NULL, arguments, argumentNames, cnt, app, pos, status);
return appendTo;
}
namespace {
/**
* Mutable input/output values for the PluralSelectorProvider.
* Separate so that it is possible to make MessageFormat Freezable.
*/
class PluralSelectorContext {
public:
PluralSelectorContext(int32_t start, const UnicodeString &name,
const Formattable &num, double off, UErrorCode &errorCode)
: startIndex(start), argName(name), offset(off),
numberArgIndex(-1), formatter(NULL), forReplaceNumber(FALSE) {
// number needs to be set even when select() is not called.
// Keep it as a Number/Formattable:
// For format() methods, and to preserve information (e.g., BigDecimal).
if(off == 0) {
number = num;
} else {
number = num.getDouble(errorCode) - off;
}
}
// Input values for plural selection with decimals.
int32_t startIndex;
const UnicodeString &argName;
/** argument number - plural offset */
Formattable number;
double offset;
// Output values for plural selection with decimals.
/** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
int32_t numberArgIndex;
const Format *formatter;
/** formatted argument number - plural offset */
UnicodeString numberString;
/** TRUE if number-offset was formatted with the stock number formatter */
UBool forReplaceNumber;
};
} // namespace
// if argumentNames is NULL, this means arguments is a numeric array.
// arguments can not be NULL.
// We use const void *plNumber rather than const PluralSelectorContext *pluralNumber
// so that we need not declare the PluralSelectorContext in the public header file.
void MessageFormat::format(int32_t msgStart, const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
AppendableWrapper& appendTo,
FieldPosition* ignore,
UErrorCode& success) const {
if (U_FAILURE(success)) {
return;
}
const UnicodeString& msgString = msgPattern.getPatternString();
int32_t prevIndex = msgPattern.getPart(msgStart).getLimit();
for (int32_t i = msgStart + 1; U_SUCCESS(success) ; ++i) {
const MessagePattern::Part* part = &msgPattern.getPart(i);
const UMessagePatternPartType type = part->getType();
int32_t index = part->getIndex();
appendTo.append(msgString, prevIndex, index - prevIndex);
if (type == UMSGPAT_PART_TYPE_MSG_LIMIT) {
return;
}
prevIndex = part->getLimit();
if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
const PluralSelectorContext &pluralNumber =
*static_cast<const PluralSelectorContext *>(plNumber);
if(pluralNumber.forReplaceNumber) {
// number-offset was already formatted.
appendTo.formatAndAppend(pluralNumber.formatter,
pluralNumber.number, pluralNumber.numberString, success);
} else {
const NumberFormat* nf = getDefaultNumberFormat(success);
appendTo.formatAndAppend(nf, pluralNumber.number, success);
}
continue;
}
if (type != UMSGPAT_PART_TYPE_ARG_START) {
continue;
}
int32_t argLimit = msgPattern.getLimitPartIndex(i);
UMessagePatternArgType argType = part->getArgType();
part = &msgPattern.getPart(++i);
const Formattable* arg;
UBool noArg = FALSE;
UnicodeString argName = msgPattern.getSubstring(*part);
if (argumentNames == NULL) {
int32_t argNumber = part->getValue(); // ARG_NUMBER
if (0 <= argNumber && argNumber < cnt) {
arg = arguments + argNumber;
} else {
arg = NULL;
noArg = TRUE;
}
} else {
arg = getArgFromListByName(arguments, argumentNames, cnt, argName);
if (arg == NULL) {
noArg = TRUE;
}
}
++i;
int32_t prevDestLength = appendTo.length();
const Format* formatter = NULL;
if (noArg) {
appendTo.append(
UnicodeString(LEFT_CURLY_BRACE).append(argName).append(RIGHT_CURLY_BRACE));
} else if (arg == NULL) {
appendTo.append(NULL_STRING, 4);
} else if(plNumber!=NULL &&
static_cast<const PluralSelectorContext *>(plNumber)->numberArgIndex==(i-2)) {
const PluralSelectorContext &pluralNumber =
*static_cast<const PluralSelectorContext *>(plNumber);
if(pluralNumber.offset == 0) {
// The number was already formatted with this formatter.
appendTo.formatAndAppend(pluralNumber.formatter, pluralNumber.number,
pluralNumber.numberString, success);
} else {
// Do not use the formatted (number-offset) string for a named argument
// that formats the number without subtracting the offset.
appendTo.formatAndAppend(pluralNumber.formatter, *arg, success);
}
} else if ((formatter = getCachedFormatter(i -2)) != 0) {
// Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
if (dynamic_cast<const ChoiceFormat*>(formatter) ||
dynamic_cast<const PluralFormat*>(formatter) ||
dynamic_cast<const SelectFormat*>(formatter)) {
// We only handle nested formats here if they were provided via
// setFormat() or its siblings. Otherwise they are not cached and instead
// handled below according to argType.
UnicodeString subMsgString;
formatter->format(*arg, subMsgString, success);
if (subMsgString.indexOf(LEFT_CURLY_BRACE) >= 0 ||
(subMsgString.indexOf(SINGLE_QUOTE) >= 0 && !MessageImpl::jdkAposMode(msgPattern))
) {
MessageFormat subMsgFormat(subMsgString, fLocale, success);
subMsgFormat.format(0, NULL, arguments, argumentNames, cnt, appendTo, ignore, success);
} else {
appendTo.append(subMsgString);
}
} else {
appendTo.formatAndAppend(formatter, *arg, success);
}
} else if (argType == UMSGPAT_ARG_TYPE_NONE || (cachedFormatters && uhash_iget(cachedFormatters, i - 2))) {
// We arrive here if getCachedFormatter returned NULL, but there was actually an element in the hash table.
// This can only happen if the hash table contained a DummyFormat, so the if statement above is a check
// for the hash table containind DummyFormat.
if (arg->isNumeric()) {
const NumberFormat* nf = getDefaultNumberFormat(success);
appendTo.formatAndAppend(nf, *arg, success);
} else if (arg->getType() == Formattable::kDate) {
const DateFormat* df = getDefaultDateFormat(success);
appendTo.formatAndAppend(df, *arg, success);
} else {
appendTo.append(arg->getString(success));
}
} else if (argType == UMSGPAT_ARG_TYPE_CHOICE) {
if (!arg->isNumeric()) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// We must use the Formattable::getDouble() variant with the UErrorCode parameter
// because only this one converts non-double numeric types to double.
const double number = arg->getDouble(success);
int32_t subMsgStart = ChoiceFormat::findSubMessage(msgPattern, i, number);
formatComplexSubMessage(subMsgStart, NULL, arguments, argumentNames,
cnt, appendTo, success);
} else if (UMSGPAT_ARG_TYPE_HAS_PLURAL_STYLE(argType)) {
if (!arg->isNumeric()) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
const PluralSelectorProvider &selector =
argType == UMSGPAT_ARG_TYPE_PLURAL ? pluralProvider : ordinalProvider;
// We must use the Formattable::getDouble() variant with the UErrorCode parameter
// because only this one converts non-double numeric types to double.
double offset = msgPattern.getPluralOffset(i);
PluralSelectorContext context(i, argName, *arg, offset, success);
int32_t subMsgStart = PluralFormat::findSubMessage(
msgPattern, i, selector, &context, arg->getDouble(success), success);
formatComplexSubMessage(subMsgStart, &context, arguments, argumentNames,
cnt, appendTo, success);
} else if (argType == UMSGPAT_ARG_TYPE_SELECT) {
int32_t subMsgStart = SelectFormat::findSubMessage(msgPattern, i, arg->getString(success), success);
formatComplexSubMessage(subMsgStart, NULL, arguments, argumentNames,
cnt, appendTo, success);
} else {
// This should never happen.
success = U_INTERNAL_PROGRAM_ERROR;
return;
}
ignore = updateMetaData(appendTo, prevDestLength, ignore, arg);
prevIndex = msgPattern.getPart(argLimit).getLimit();
i = argLimit;
}
}
void MessageFormat::formatComplexSubMessage(int32_t msgStart,
const void *plNumber,
const Formattable* arguments,
const UnicodeString *argumentNames,
int32_t cnt,
AppendableWrapper& appendTo,
UErrorCode& success) const {
if (U_FAILURE(success)) {
return;
}
if (!MessageImpl::jdkAposMode(msgPattern)) {
format(msgStart, plNumber, arguments, argumentNames, cnt, appendTo, NULL, success);
return;
}
// JDK compatibility mode: (see JDK MessageFormat.format() API docs)
// - remove SKIP_SYNTAX; that is, remove half of the apostrophes
// - if the result string contains an open curly brace '{' then
// instantiate a temporary MessageFormat object and format again;
// otherwise just append the result string
const UnicodeString& msgString = msgPattern.getPatternString();
UnicodeString sb;
int32_t prevIndex = msgPattern.getPart(msgStart).getLimit();
for (int32_t i = msgStart;;) {
const MessagePattern::Part& part = msgPattern.getPart(++i);
const UMessagePatternPartType type = part.getType();
int32_t index = part.getIndex();
if (type == UMSGPAT_PART_TYPE_MSG_LIMIT) {
sb.append(msgString, prevIndex, index - prevIndex);
break;
} else if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER || type == UMSGPAT_PART_TYPE_SKIP_SYNTAX) {
sb.append(msgString, prevIndex, index - prevIndex);
if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
const PluralSelectorContext &pluralNumber =
*static_cast<const PluralSelectorContext *>(plNumber);
if(pluralNumber.forReplaceNumber) {
// number-offset was already formatted.
sb.append(pluralNumber.numberString);
} else {
const NumberFormat* nf = getDefaultNumberFormat(success);
sb.append(nf->format(pluralNumber.number, sb, success));
}
}
prevIndex = part.getLimit();
} else if (type == UMSGPAT_PART_TYPE_ARG_START) {
sb.append(msgString, prevIndex, index - prevIndex);
prevIndex = index;
i = msgPattern.getLimitPartIndex(i);
index = msgPattern.getPart(i).getLimit();
MessageImpl::appendReducedApostrophes(msgString, prevIndex, index, sb);
prevIndex = index;
}
}
if (sb.indexOf(LEFT_CURLY_BRACE) >= 0) {
UnicodeString emptyPattern; // gcc 3.3.3 fails with "UnicodeString()" as the first parameter.
MessageFormat subMsgFormat(emptyPattern, fLocale, success);
subMsgFormat.applyPattern(sb, UMSGPAT_APOS_DOUBLE_REQUIRED, NULL, success);
subMsgFormat.format(0, NULL, arguments, argumentNames, cnt, appendTo, NULL, success);
} else {
appendTo.append(sb);
}
}
UnicodeString MessageFormat::getLiteralStringUntilNextArgument(int32_t from) const {
const UnicodeString& msgString=msgPattern.getPatternString();
int32_t prevIndex=msgPattern.getPart(from).getLimit();
UnicodeString b;
for (int32_t i = from + 1; ; ++i) {
const MessagePattern::Part& part = msgPattern.getPart(i);
const UMessagePatternPartType type=part.getType();
int32_t index=part.getIndex();
b.append(msgString, prevIndex, index - prevIndex);
if(type==UMSGPAT_PART_TYPE_ARG_START || type==UMSGPAT_PART_TYPE_MSG_LIMIT) {
return b;
}
// Unexpected Part "part" in parsed message.
U_ASSERT(type==UMSGPAT_PART_TYPE_SKIP_SYNTAX || type==UMSGPAT_PART_TYPE_INSERT_CHAR);
prevIndex=part.getLimit();
}
}
FieldPosition* MessageFormat::updateMetaData(AppendableWrapper& /*dest*/, int32_t /*prevLength*/,
FieldPosition* /*fp*/, const Formattable* /*argId*/) const {
// Unlike in Java, there are no field attributes defined for MessageFormat. Do nothing.
return NULL;
/*
if (fp != NULL && Field.ARGUMENT.equals(fp.getFieldAttribute())) {
fp->setBeginIndex(prevLength);
fp->setEndIndex(dest.get_length());
return NULL;
}
return fp;
*/
}
int32_t
MessageFormat::findOtherSubMessage(int32_t partIndex) const {
int32_t count=msgPattern.countParts();
const MessagePattern::Part *part = &msgPattern.getPart(partIndex);
if(MessagePattern::Part::hasNumericValue(part->getType())) {
++partIndex;
}
// Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
// until ARG_LIMIT or end of plural-only pattern.
UnicodeString other(FALSE, OTHER_STRING, 5);
do {
part=&msgPattern.getPart(partIndex++);
UMessagePatternPartType type=part->getType();
if(type==UMSGPAT_PART_TYPE_ARG_LIMIT) {
break;
}
U_ASSERT(type==UMSGPAT_PART_TYPE_ARG_SELECTOR);
// part is an ARG_SELECTOR followed by an optional explicit value, and then a message
if(msgPattern.partSubstringMatches(*part, other)) {
return partIndex;
}
if(MessagePattern::Part::hasNumericValue(msgPattern.getPartType(partIndex))) {
++partIndex; // skip the numeric-value part of "=1" etc.
}
partIndex=msgPattern.getLimitPartIndex(partIndex);
} while(++partIndex<count);
return 0;
}
int32_t
MessageFormat::findFirstPluralNumberArg(int32_t msgStart, const UnicodeString &argName) const {
for(int32_t i=msgStart+1;; ++i) {
const MessagePattern::Part &part=msgPattern.getPart(i);
UMessagePatternPartType type=part.getType();
if(type==UMSGPAT_PART_TYPE_MSG_LIMIT) {
return 0;
}
if(type==UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
return -1;
}
if(type==UMSGPAT_PART_TYPE_ARG_START) {
UMessagePatternArgType argType=part.getArgType();
if(!argName.isEmpty() && (argType==UMSGPAT_ARG_TYPE_NONE || argType==UMSGPAT_ARG_TYPE_SIMPLE)) {
// ARG_NUMBER or ARG_NAME
if(msgPattern.partSubstringMatches(msgPattern.getPart(i+1), argName)) {
return i;
}
}
i=msgPattern.getLimitPartIndex(i);
}
}
}
void MessageFormat::copyObjects(const MessageFormat& that, UErrorCode& ec) {
// Deep copy pointer fields.
// We need not copy the formatAliases because they are re-filled
// in each getFormats() call.
// The defaultNumberFormat, defaultDateFormat and pluralProvider.rules
// also get created on demand.
argTypeCount = that.argTypeCount;
if (argTypeCount > 0) {
if (!allocateArgTypes(argTypeCount, ec)) {
return;
}
uprv_memcpy(argTypes, that.argTypes, argTypeCount * sizeof(argTypes[0]));
}
if (cachedFormatters != NULL) {
uhash_removeAll(cachedFormatters);
}
if (customFormatArgStarts != NULL) {
uhash_removeAll(customFormatArgStarts);
}
if (that.cachedFormatters) {
if (cachedFormatters == NULL) {
cachedFormatters=uhash_open(uhash_hashLong, uhash_compareLong,
equalFormatsForHash, &ec);
if (U_FAILURE(ec)) {
return;
}
uhash_setValueDeleter(cachedFormatters, uprv_deleteUObject);
}
const int32_t count = uhash_count(that.cachedFormatters);
int32_t pos, idx;
for (idx = 0, pos = UHASH_FIRST; idx < count && U_SUCCESS(ec); ++idx) {
const UHashElement* cur = uhash_nextElement(that.cachedFormatters, &pos);
Format* newFormat = ((Format*)(cur->value.pointer))->clone();
if (newFormat) {
uhash_iput(cachedFormatters, cur->key.integer, newFormat, &ec);
} else {
ec = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
}
if (that.customFormatArgStarts) {
if (customFormatArgStarts == NULL) {
customFormatArgStarts=uhash_open(uhash_hashLong, uhash_compareLong,
NULL, &ec);
}
const int32_t count = uhash_count(that.customFormatArgStarts);
int32_t pos, idx;
for (idx = 0, pos = UHASH_FIRST; idx < count && U_SUCCESS(ec); ++idx) {
const UHashElement* cur = uhash_nextElement(that.customFormatArgStarts, &pos);
uhash_iputi(customFormatArgStarts, cur->key.integer, cur->value.integer, &ec);
}
}
}
Formattable*
MessageFormat::parse(int32_t msgStart,
const UnicodeString& source,
ParsePosition& pos,
int32_t& count,
UErrorCode& ec) const {
count = 0;
if (U_FAILURE(ec)) {
pos.setErrorIndex(pos.getIndex());
return NULL;
}
// parse() does not work with named arguments.
if (msgPattern.hasNamedArguments()) {
ec = U_ARGUMENT_TYPE_MISMATCH;
pos.setErrorIndex(pos.getIndex());
return NULL;
}
LocalArray<Formattable> resultArray(new Formattable[argTypeCount ? argTypeCount : 1]);
const UnicodeString& msgString=msgPattern.getPatternString();
int32_t prevIndex=msgPattern.getPart(msgStart).getLimit();
int32_t sourceOffset = pos.getIndex();
ParsePosition tempStatus(0);
for(int32_t i=msgStart+1; ; ++i) {
UBool haveArgResult = FALSE;
const MessagePattern::Part* part=&msgPattern.getPart(i);
const UMessagePatternPartType type=part->getType();
int32_t index=part->getIndex();
// Make sure the literal string matches.
int32_t len = index - prevIndex;
if (len == 0 || (0 == msgString.compare(prevIndex, len, source, sourceOffset, len))) {
sourceOffset += len;
prevIndex += len;
} else {
pos.setErrorIndex(sourceOffset);
return NULL; // leave index as is to signal error
}
if(type==UMSGPAT_PART_TYPE_MSG_LIMIT) {
// Things went well! Done.
pos.setIndex(sourceOffset);
return resultArray.orphan();
}
if(type==UMSGPAT_PART_TYPE_SKIP_SYNTAX || type==UMSGPAT_PART_TYPE_INSERT_CHAR) {
prevIndex=part->getLimit();
continue;
}
// We do not support parsing Plural formats. (No REPLACE_NUMBER here.)
// Unexpected Part "part" in parsed message.
U_ASSERT(type==UMSGPAT_PART_TYPE_ARG_START);
int32_t argLimit=msgPattern.getLimitPartIndex(i);
UMessagePatternArgType argType=part->getArgType();
part=&msgPattern.getPart(++i);
int32_t argNumber = part->getValue(); // ARG_NUMBER
UnicodeString key;
++i;
const Format* formatter = NULL;
Formattable& argResult = resultArray[argNumber];
if(cachedFormatters!=NULL && (formatter = getCachedFormatter(i - 2))!=NULL) {
// Just parse using the formatter.
tempStatus.setIndex(sourceOffset);
formatter->parseObject(source, argResult, tempStatus);
if (tempStatus.getIndex() == sourceOffset) {
pos.setErrorIndex(sourceOffset);
return NULL; // leave index as is to signal error
}
sourceOffset = tempStatus.getIndex();
haveArgResult = TRUE;
} else if(
argType==UMSGPAT_ARG_TYPE_NONE || (cachedFormatters && uhash_iget(cachedFormatters, i -2))) {
// We arrive here if getCachedFormatter returned NULL, but there was actually an element in the hash table.
// This can only happen if the hash table contained a DummyFormat, so the if statement above is a check
// for the hash table containind DummyFormat.
// Match as a string.
// if at end, use longest possible match
// otherwise uses first match to intervening string
// does NOT recursively try all possibilities
UnicodeString stringAfterArgument = getLiteralStringUntilNextArgument(argLimit);
int32_t next;
if (!stringAfterArgument.isEmpty()) {
next = source.indexOf(stringAfterArgument, sourceOffset);
} else {
next = source.length();
}
if (next < 0) {
pos.setErrorIndex(sourceOffset);
return NULL; // leave index as is to signal error
} else {
UnicodeString strValue(source.tempSubString(sourceOffset, next - sourceOffset));
UnicodeString compValue;
compValue.append(LEFT_CURLY_BRACE);
itos(argNumber, compValue);
compValue.append(RIGHT_CURLY_BRACE);
if (0 != strValue.compare(compValue)) {
argResult.setString(strValue);
haveArgResult = TRUE;
}
sourceOffset = next;
}
} else if(argType==UMSGPAT_ARG_TYPE_CHOICE) {
tempStatus.setIndex(sourceOffset);
double choiceResult = ChoiceFormat::parseArgument(msgPattern, i, source, tempStatus);
if (tempStatus.getIndex() == sourceOffset) {
pos.setErrorIndex(sourceOffset);
return NULL; // leave index as is to signal error
}
argResult.setDouble(choiceResult);
haveArgResult = TRUE;
sourceOffset = tempStatus.getIndex();
} else if(UMSGPAT_ARG_TYPE_HAS_PLURAL_STYLE(argType) || argType==UMSGPAT_ARG_TYPE_SELECT) {
// Parsing not supported.
ec = U_UNSUPPORTED_ERROR;
return NULL;
} else {
// This should never happen.
ec = U_INTERNAL_PROGRAM_ERROR;
return NULL;
}
if (haveArgResult && count <= argNumber) {
count = argNumber + 1;
}
prevIndex=msgPattern.getPart(argLimit).getLimit();
i=argLimit;
}
}
// -------------------------------------
// Parses the source pattern and returns the Formattable objects array,
// the array count and the ending parse position. The caller of this method
// owns the array.
Formattable*
MessageFormat::parse(const UnicodeString& source,
ParsePosition& pos,
int32_t& count) const {
UErrorCode ec = U_ZERO_ERROR;
return parse(0, source, pos, count, ec);
}
// -------------------------------------
// Parses the source string and returns the array of
// Formattable objects and the array count. The caller
// owns the returned array.
Formattable*
MessageFormat::parse(const UnicodeString& source,
int32_t& cnt,
UErrorCode& success) const
{
if (msgPattern.hasNamedArguments()) {
success = U_ARGUMENT_TYPE_MISMATCH;
return NULL;
}
ParsePosition status(0);
// Calls the actual implementation method and starts
// from zero offset of the source text.
Formattable* result = parse(source, status, cnt);
if (status.getIndex() == 0) {
success = U_MESSAGE_PARSE_ERROR;
delete[] result;
return NULL;
}
return result;
}
// -------------------------------------
// Parses the source text and copy into the result buffer.
void
MessageFormat::parseObject( const UnicodeString& source,
Formattable& result,
ParsePosition& status) const
{
int32_t cnt = 0;
Formattable* tmpResult = parse(source, status, cnt);
if (tmpResult != NULL)
result.adoptArray(tmpResult, cnt);
}
UnicodeString
MessageFormat::autoQuoteApostrophe(const UnicodeString& pattern, UErrorCode& status) {
UnicodeString result;
if (U_SUCCESS(status)) {
int32_t plen = pattern.length();
const UChar* pat = pattern.getBuffer();
int32_t blen = plen * 2 + 1; // space for null termination, convenience
UChar* buf = result.getBuffer(blen);
if (buf == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
int32_t len = umsg_autoQuoteApostrophe(pat, plen, buf, blen, &status);
result.releaseBuffer(U_SUCCESS(status) ? len : 0);
}
}
if (U_FAILURE(status)) {
result.setToBogus();
}
return result;
}
// -------------------------------------
static Format* makeRBNF(URBNFRuleSetTag tag, const Locale& locale, const UnicodeString& defaultRuleSet, UErrorCode& ec) {
RuleBasedNumberFormat* fmt = new RuleBasedNumberFormat(tag, locale, ec);
if (fmt == NULL) {
ec = U_MEMORY_ALLOCATION_ERROR;
} else if (U_SUCCESS(ec) && defaultRuleSet.length() > 0) {
UErrorCode localStatus = U_ZERO_ERROR; // ignore unrecognized default rule set
fmt->setDefaultRuleSet(defaultRuleSet, localStatus);
}
return fmt;
}
void MessageFormat::cacheExplicitFormats(UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
if (cachedFormatters != NULL) {
uhash_removeAll(cachedFormatters);
}
if (customFormatArgStarts != NULL) {
uhash_removeAll(customFormatArgStarts);
}
// The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT
// which we need not examine.
int32_t limit = msgPattern.countParts() - 2;
argTypeCount = 0;
// We also need not look at the first two "parts"
// (at most MSG_START and ARG_START) in this loop.
// We determine the argTypeCount first so that we can allocateArgTypes
// so that the next loop can set argTypes[argNumber].
// (This is for the C API which needs the argTypes to read its va_arg list.)
for (int32_t i = 2; i < limit && U_SUCCESS(status); ++i) {
const MessagePattern::Part& part = msgPattern.getPart(i);
if (part.getType() == UMSGPAT_PART_TYPE_ARG_NUMBER) {
const int argNumber = part.getValue();
if (argNumber >= argTypeCount) {
argTypeCount = argNumber + 1;
}
}
}
if (!allocateArgTypes(argTypeCount, status)) {
return;
}
// Set all argTypes to kObject, as a "none" value, for lack of any better value.
// We never use kObject for real arguments.
// We use it as "no argument yet" for the check for hasArgTypeConflicts.
for (int32_t i = 0; i < argTypeCount; ++i) {
argTypes[i] = Formattable::kObject;
}
hasArgTypeConflicts = FALSE;
// This loop starts at part index 1 because we do need to examine
// ARG_START parts. (But we can ignore the MSG_START.)
for (int32_t i = 1; i < limit && U_SUCCESS(status); ++i) {
const MessagePattern::Part* part = &msgPattern.getPart(i);
if (part->getType() != UMSGPAT_PART_TYPE_ARG_START) {
continue;
}
UMessagePatternArgType argType = part->getArgType();
int32_t argNumber = -1;
part = &msgPattern.getPart(i + 1);
if (part->getType() == UMSGPAT_PART_TYPE_ARG_NUMBER) {
argNumber = part->getValue();
}
Formattable::Type formattableType;
switch (argType) {
case UMSGPAT_ARG_TYPE_NONE:
formattableType = Formattable::kString;
break;
case UMSGPAT_ARG_TYPE_SIMPLE: {
int32_t index = i;
i += 2;
UnicodeString explicitType = msgPattern.getSubstring(msgPattern.getPart(i++));
UnicodeString style;
if ((part = &msgPattern.getPart(i))->getType() == UMSGPAT_PART_TYPE_ARG_STYLE) {
style = msgPattern.getSubstring(*part);
++i;
}
UParseError parseError;
Format* formatter = createAppropriateFormat(explicitType, style, formattableType, parseError, status);
setArgStartFormat(index, formatter, status);
break;
}
case UMSGPAT_ARG_TYPE_CHOICE:
case UMSGPAT_ARG_TYPE_PLURAL:
case UMSGPAT_ARG_TYPE_SELECTORDINAL:
formattableType = Formattable::kDouble;
break;
case UMSGPAT_ARG_TYPE_SELECT:
formattableType = Formattable::kString;
break;
default:
status = U_INTERNAL_PROGRAM_ERROR; // Should be unreachable.
formattableType = Formattable::kString;
break;
}
if (argNumber != -1) {
if (argTypes[argNumber] != Formattable::kObject && argTypes[argNumber] != formattableType) {
hasArgTypeConflicts = TRUE;
}
argTypes[argNumber] = formattableType;
}
}
}
Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeString& style,
Formattable::Type& formattableType, UParseError& parseError,
UErrorCode& ec) {
if (U_FAILURE(ec)) {
return NULL;
}
Format* fmt = NULL;
int32_t typeID, styleID;
DateFormat::EStyle date_style;
int32_t firstNonSpace;
switch (typeID = findKeyword(type, TYPE_IDS)) {
case 0: // number
formattableType = Formattable::kDouble;
switch (findKeyword(style, NUMBER_STYLE_IDS)) {
case 0: // default
fmt = NumberFormat::createInstance(fLocale, ec);
break;
case 1: // currency
fmt = NumberFormat::createCurrencyInstance(fLocale, ec);
break;
case 2: // percent
fmt = NumberFormat::createPercentInstance(fLocale, ec);
break;
case 3: // integer
formattableType = Formattable::kLong;
fmt = createIntegerFormat(fLocale, ec);
break;
default: // pattern or skeleton
firstNonSpace = PatternProps::skipWhiteSpace(style, 0);
if (style.compare(firstNonSpace, 2, u"::", 0, 2) == 0) {
// Skeleton
UnicodeString skeleton = style.tempSubString(firstNonSpace + 2);
fmt = number::NumberFormatter::forSkeleton(skeleton, ec).locale(fLocale).toFormat(ec);
} else {
// Pattern
fmt = NumberFormat::createInstance(fLocale, ec);
if (fmt) {
auto* decfmt = dynamic_cast<DecimalFormat*>(fmt);
if (decfmt != nullptr) {
decfmt->applyPattern(style, parseError, ec);
}
}
}
break;
}
break;
case 1: // date
case 2: // time
formattableType = Formattable::kDate;
firstNonSpace = PatternProps::skipWhiteSpace(style, 0);
if (style.compare(firstNonSpace, 2, u"::", 0, 2) == 0) {
// Skeleton
UnicodeString skeleton = style.tempSubString(firstNonSpace + 2);
fmt = DateFormat::createInstanceForSkeleton(skeleton, fLocale, ec);
} else {
// Pattern
styleID = findKeyword(style, DATE_STYLE_IDS);
date_style = (styleID >= 0) ? DATE_STYLES[styleID] : DateFormat::kDefault;
if (typeID == 1) {
fmt = DateFormat::createDateInstance(date_style, fLocale);
} else {
fmt = DateFormat::createTimeInstance(date_style, fLocale);
}
if (styleID < 0 && fmt != NULL) {
SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fmt);
if (sdtfmt != NULL) {
sdtfmt->applyPattern(style);
}
}
}
break;
case 3: // spellout
formattableType = Formattable::kDouble;
fmt = makeRBNF(URBNF_SPELLOUT, fLocale, style, ec);
break;
case 4: // ordinal
formattableType = Formattable::kDouble;
fmt = makeRBNF(URBNF_ORDINAL, fLocale, style, ec);
break;
case 5: // duration
formattableType = Formattable::kDouble;
fmt = makeRBNF(URBNF_DURATION, fLocale, style, ec);
break;
default:
formattableType = Formattable::kString;
ec = U_ILLEGAL_ARGUMENT_ERROR;
break;
}
return fmt;
}
//-------------------------------------
// Finds the string, s, in the string array, list.
int32_t MessageFormat::findKeyword(const UnicodeString& s,
const UChar * const *list)
{
if (s.isEmpty()) {
return 0; // default
}
int32_t length = s.length();
const UChar *ps = PatternProps::trimWhiteSpace(s.getBuffer(), length);
UnicodeString buffer(FALSE, ps, length);
// Trims the space characters and turns all characters
// in s to lower case.
buffer.toLower("");
for (int32_t i = 0; list[i]; ++i) {
if (!buffer.compare(list[i], u_strlen(list[i]))) {
return i;
}
}
return -1;
}
/**
* Convenience method that ought to be in NumberFormat
*/
NumberFormat*
MessageFormat::createIntegerFormat(const Locale& locale, UErrorCode& status) const {
NumberFormat *temp = NumberFormat::createInstance(locale, status);
DecimalFormat *temp2;
if (temp != NULL && (temp2 = dynamic_cast<DecimalFormat*>(temp)) != NULL) {
temp2->setMaximumFractionDigits(0);
temp2->setDecimalSeparatorAlwaysShown(FALSE);
temp2->setParseIntegerOnly(TRUE);
}
return temp;
}
/**
* Return the default number format. Used to format a numeric
* argument when subformats[i].format is NULL. Returns NULL
* on failure.
*
* Semantically const but may modify *this.
*/
const NumberFormat* MessageFormat::getDefaultNumberFormat(UErrorCode& ec) const {
if (defaultNumberFormat == NULL) {
MessageFormat* t = (MessageFormat*) this;
t->defaultNumberFormat = NumberFormat::createInstance(fLocale, ec);
if (U_FAILURE(ec)) {
delete t->defaultNumberFormat;
t->defaultNumberFormat = NULL;
} else if (t->defaultNumberFormat == NULL) {
ec = U_MEMORY_ALLOCATION_ERROR;
}
}
return defaultNumberFormat;
}
/**
* Return the default date format. Used to format a date
* argument when subformats[i].format is NULL. Returns NULL
* on failure.
*
* Semantically const but may modify *this.
*/
const DateFormat* MessageFormat::getDefaultDateFormat(UErrorCode& ec) const {
if (defaultDateFormat == NULL) {
MessageFormat* t = (MessageFormat*) this;
t->defaultDateFormat = DateFormat::createDateTimeInstance(DateFormat::kShort, DateFormat::kShort, fLocale);
if (t->defaultDateFormat == NULL) {
ec = U_MEMORY_ALLOCATION_ERROR;
}
}
return defaultDateFormat;
}
UBool
MessageFormat::usesNamedArguments() const {
return msgPattern.hasNamedArguments();
}
int32_t
MessageFormat::getArgTypeCount() const {
return argTypeCount;
}
UBool MessageFormat::equalFormats(const void* left, const void* right) {
return *(const Format*)left==*(const Format*)right;
}
UBool MessageFormat::DummyFormat::operator==(const Format&) const {
return TRUE;
}
MessageFormat::DummyFormat* MessageFormat::DummyFormat::clone() const {
return new DummyFormat();
}
UnicodeString& MessageFormat::DummyFormat::format(const Formattable&,
UnicodeString& appendTo,
UErrorCode& status) const {
if (U_SUCCESS(status)) {
status = U_UNSUPPORTED_ERROR;
}
return appendTo;
}
UnicodeString& MessageFormat::DummyFormat::format(const Formattable&,
UnicodeString& appendTo,
FieldPosition&,
UErrorCode& status) const {
if (U_SUCCESS(status)) {
status = U_UNSUPPORTED_ERROR;
}
return appendTo;
}
UnicodeString& MessageFormat::DummyFormat::format(const Formattable&,
UnicodeString& appendTo,
FieldPositionIterator*,
UErrorCode& status) const {
if (U_SUCCESS(status)) {
status = U_UNSUPPORTED_ERROR;
}
return appendTo;
}
void MessageFormat::DummyFormat::parseObject(const UnicodeString&,
Formattable&,
ParsePosition& ) const {
}
FormatNameEnumeration::FormatNameEnumeration(UVector *fNameList, UErrorCode& /*status*/) {
pos=0;
fFormatNames = fNameList;
}
const UnicodeString*
FormatNameEnumeration::snext(UErrorCode& status) {
if (U_SUCCESS(status) && pos < fFormatNames->size()) {
return (const UnicodeString*)fFormatNames->elementAt(pos++);
}
return NULL;
}
void
FormatNameEnumeration::reset(UErrorCode& /*status*/) {
pos=0;
}
int32_t
FormatNameEnumeration::count(UErrorCode& /*status*/) const {
return (fFormatNames==NULL) ? 0 : fFormatNames->size();
}
FormatNameEnumeration::~FormatNameEnumeration() {
delete fFormatNames;
}
MessageFormat::PluralSelectorProvider::PluralSelectorProvider(const MessageFormat &mf, UPluralType t)
: msgFormat(mf), rules(NULL), type(t) {
}
MessageFormat::PluralSelectorProvider::~PluralSelectorProvider() {
delete rules;
}
UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double number,
UErrorCode& ec) const {
if (U_FAILURE(ec)) {
return UnicodeString(FALSE, OTHER_STRING, 5);
}
MessageFormat::PluralSelectorProvider* t = const_cast<MessageFormat::PluralSelectorProvider*>(this);
if(rules == NULL) {
t->rules = PluralRules::forLocale(msgFormat.fLocale, type, ec);
if (U_FAILURE(ec)) {
return UnicodeString(FALSE, OTHER_STRING, 5);
}
}
// Select a sub-message according to how the number is formatted,
// which is specified in the selected sub-message.
// We avoid this circle by looking at how
// the number is formatted in the "other" sub-message
// which must always be present and usually contains the number.
// Message authors should be consistent across sub-messages.
PluralSelectorContext &context = *static_cast<PluralSelectorContext *>(ctx);
int32_t otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != NULL) {
context.formatter =
(const Format*)uhash_iget(msgFormat.cachedFormatters, context.numberArgIndex);
}
if(context.formatter == NULL) {
context.formatter = msgFormat.getDefaultNumberFormat(ec);
context.forReplaceNumber = TRUE;
}
if (context.number.getDouble(ec) != number) {
ec = U_INTERNAL_PROGRAM_ERROR;
return UnicodeString(FALSE, OTHER_STRING, 5);
}
context.formatter->format(context.number, context.numberString, ec);
auto* decFmt = dynamic_cast<const DecimalFormat *>(context.formatter);
if(decFmt != NULL) {
number::impl::DecimalQuantity dq;
decFmt->formatToDecimalQuantity(context.number, dq, ec);
if (U_FAILURE(ec)) {
return UnicodeString(FALSE, OTHER_STRING, 5);
}
return rules->select(dq);
} else {
return rules->select(number);
}
}
void MessageFormat::PluralSelectorProvider::reset() {
delete rules;
rules = NULL;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof