scuffed-code/icu4c/source/i18n/msgfmt.cpp
2005-05-27 22:07:16 +00:00

1469 lines
46 KiB
C++

/*
*******************************************************************************
* Copyright (C) 1997-2004, 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
********************************************************************************
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "unicode/msgfmt.h"
#include "unicode/decimfmt.h"
#include "unicode/datefmt.h"
#include "unicode/smpdtfmt.h"
#include "unicode/choicfmt.h"
#include "unicode/ustring.h"
#include "unicode/ucnv_err.h"
#include "unicode/uchar.h"
#include "unicode/umsg.h"
#include "unicode/rbnf.h"
#include "ustrfmt.h"
#include "cmemory.h"
#include "util.h"
#include "uassert.h"
// *****************************************************************************
// class MessageFormat
// *****************************************************************************
#define COMMA ((UChar)0x002C)
#define SINGLE_QUOTE ((UChar)0x0027)
#define LEFT_CURLY_BRACE ((UChar)0x007B)
#define RIGHT_CURLY_BRACE ((UChar)0x007D)
//---------------------------------------
// static data
static const UChar ID_EMPTY[] = {
0 /* empty string, used for default so that null can mark end of list */
};
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_CHOICE[] = {
0x63, 0x68, 0x6F, 0x69, 0x63, 0x65, 0 /* "choice" */
};
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_EMPTY,
ID_NUMBER,
ID_DATE,
ID_TIME,
ID_CHOICE,
ID_SPELLOUT,
ID_ORDINAL,
ID_DURATION,
NULL,
};
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 DateFormat::EStyle DATE_STYLES[] = {
DateFormat::kDefault,
DateFormat::kShort,
DateFormat::kMedium,
DateFormat::kLong,
DateFormat::kFull,
};
static const int32_t DEFAULT_INITIAL_CAPACITY = 10;
U_NAMESPACE_BEGIN
// -------------------------------------
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MessageFormat)
//--------------------------------------------------------------------
/**
* Convert a string to an unsigned decimal, ignoring rule whitespace.
* @return a non-negative number if successful, or a negative number
* upon failure.
*/
static int32_t stou(const UnicodeString& string) {
int32_t n = 0;
int32_t count = 0;
UChar32 c;
for (int32_t i=0; i<string.length(); i+=U16_LENGTH(c)) {
c = string.char32At(i);
if (uprv_isRuleWhiteSpace(c)) {
continue;
}
int32_t d = u_digit(c, 10);
if (d < 0 || ++count > 10) {
return -1;
}
n = 10*n + d;
}
return n;
}
/**
* 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);
return appendTo;
}
// -------------------------------------
// Creates a MessageFormat instance based on the pattern.
MessageFormat::MessageFormat(const UnicodeString& pattern,
UErrorCode& success)
: fLocale(Locale::getDefault()), // Uses the default locale
formatAliases(NULL),
formatAliasesCapacity(0),
subformats(NULL),
subformatCount(0),
subformatCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
defaultNumberFormat(NULL),
defaultDateFormat(NULL)
{
if (!allocateSubformats(DEFAULT_INITIAL_CAPACITY) ||
!allocateArgTypes(DEFAULT_INITIAL_CAPACITY)) {
success = U_MEMORY_ALLOCATION_ERROR;
return;
}
applyPattern(pattern, success);
setLocaleIDs(fLocale.getName(), fLocale.getName());
}
MessageFormat::MessageFormat(const UnicodeString& pattern,
const Locale& newLocale,
UErrorCode& success)
: fLocale(newLocale),
formatAliases(NULL),
formatAliasesCapacity(0),
subformats(NULL),
subformatCount(0),
subformatCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
defaultNumberFormat(NULL),
defaultDateFormat(NULL)
{
if (!allocateSubformats(DEFAULT_INITIAL_CAPACITY) ||
!allocateArgTypes(DEFAULT_INITIAL_CAPACITY)) {
success = U_MEMORY_ALLOCATION_ERROR;
return;
}
applyPattern(pattern, success);
setLocaleIDs(fLocale.getName(), fLocale.getName());
}
MessageFormat::MessageFormat(const UnicodeString& pattern,
const Locale& newLocale,
UParseError& parseError,
UErrorCode& success)
: fLocale(newLocale),
formatAliases(NULL),
formatAliasesCapacity(0),
subformats(NULL),
subformatCount(0),
subformatCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
defaultNumberFormat(NULL),
defaultDateFormat(NULL)
{
if (!allocateSubformats(DEFAULT_INITIAL_CAPACITY) ||
!allocateArgTypes(DEFAULT_INITIAL_CAPACITY)) {
success = U_MEMORY_ALLOCATION_ERROR;
return;
}
applyPattern(pattern, parseError, success);
setLocaleIDs(fLocale.getName(), fLocale.getName());
}
MessageFormat::MessageFormat(const MessageFormat& that)
: Format(that),
formatAliases(NULL),
formatAliasesCapacity(0),
subformats(NULL),
subformatCount(0),
subformatCapacity(0),
argTypes(NULL),
argTypeCount(0),
argTypeCapacity(0),
defaultNumberFormat(NULL),
defaultDateFormat(NULL)
{
*this = that;
}
MessageFormat::~MessageFormat()
{
int32_t idx;
for (idx = 0; idx < subformatCount; idx++) {
delete subformats[idx].format;
}
uprv_free(subformats);
subformats = NULL;
subformatCount = subformatCapacity = 0;
uprv_free(argTypes);
argTypes = NULL;
argTypeCount = argTypeCapacity = 0;
uprv_free(formatAliases);
delete defaultNumberFormat;
delete defaultDateFormat;
}
//--------------------------------------------------------------------
// Variable-size array management
/**
* Allocate subformats[] to at least the given capacity and return
* TRUE if successful. If not, leave subformats[] unchanged.
*
* If subformats is NULL, allocate it. If it is not NULL, enlarge it
* if necessary to be at least as large as specified.
*/
UBool MessageFormat::allocateSubformats(int32_t capacity) {
if (subformats == NULL) {
subformats = (Subformat*) uprv_malloc(sizeof(*subformats) * capacity);
subformatCapacity = capacity;
subformatCount = 0;
if (subformats == NULL) {
subformatCapacity = 0;
return FALSE;
}
} else if (subformatCapacity < capacity) {
if (capacity < 2*subformatCapacity) {
capacity = 2*subformatCapacity;
}
Subformat* a = (Subformat*)
uprv_realloc(subformats, sizeof(*subformats) * capacity);
if (a == NULL) {
return FALSE; // request failed
}
subformats = a;
subformatCapacity = capacity;
}
return TRUE;
}
/**
* 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) {
if (argTypes == NULL) {
argTypes = (Formattable::Type*) uprv_malloc(sizeof(*argTypes) * capacity);
argTypeCount = 0;
argTypeCapacity = capacity;
if (argTypes == NULL) {
argTypeCapacity = 0;
return FALSE;
}
for (int32_t i=0; i<capacity; ++i) {
argTypes[i] = Formattable::kString;
}
} else if (argTypeCapacity < capacity) {
if (capacity < 2*argTypeCapacity) {
capacity = 2*argTypeCapacity;
}
Formattable::Type* a = (Formattable::Type*)
uprv_realloc(argTypes, sizeof(*argTypes) * capacity);
if (a == NULL) {
return FALSE; // request failed
}
for (int32_t i=argTypeCapacity; i<capacity; ++i) {
a[i] = Formattable::kString;
}
argTypes = a;
argTypeCapacity = capacity;
}
return TRUE;
}
// -------------------------------------
// assignment operator
const MessageFormat&
MessageFormat::operator=(const MessageFormat& that)
{
// Reallocate the arrays BEFORE changing this object
if (this != &that &&
allocateSubformats(that.subformatCount) &&
allocateArgTypes(that.argTypeCount)) {
// Calls the super class for assignment first.
Format::operator=(that);
fPattern = that.fPattern;
setLocale(that.fLocale);
int32_t j;
for (j=0; j<subformatCount; ++j) {
delete subformats[j].format;
}
subformatCount = 0;
for (j=0; j<that.subformatCount; ++j) {
// Subformat::operator= does NOT delete this.format
subformats[j] = that.subformats[j];
}
subformatCount = that.subformatCount;
for (j=0; j<that.argTypeCount; ++j) {
argTypes[j] = that.argTypes[j];
}
argTypeCount = that.argTypeCount;
}
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) ||
fPattern != that.fPattern ||
fLocale != that.fLocale) {
return FALSE;
}
int32_t j;
for (j=0; j<subformatCount; ++j) {
if (subformats[j] != that.subformats[j]) {
return FALSE;
}
}
return TRUE;
}
// -------------------------------------
// Creates a copy of this MessageFormat, the caller owns the copy.
Format*
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());
}
// -------------------------------------
// 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;
}
// The pattern is broken up into segments. Each time a subformat
// is encountered, 4 segments are recorded. For example, consider
// the pattern:
// "There {0,choice,0.0#are no files|1.0#is one file|1.0<are {0, number} files} on disk {1}."
// The first set of segments is:
// segments[0] = "There "
// segments[1] = "0"
// segments[2] = "choice"
// segments[3] = "0.0#are no files|1.0#is one file|1.0<are {0, number} files"
// During parsing, the plain text is accumulated into segments[0].
// Segments 1..3 are used to parse each subpattern. Each time a
// subpattern is parsed, it creates a format object that is stored
// in the subformats array, together with an offset and argument
// number. The offset into the plain text stored in
// segments[0].
// Quotes in segment 0 are handled normally. They are removed.
// Quotes may not occur in segments 1 or 2.
// Quotes in segment 3 are parsed and _copied_. This makes
// subformat patterns work, e.g., {1,number,'#'.##} passes
// the pattern "'#'.##" to DecimalFormat.
UnicodeString segments[4];
int32_t part = 0; // segment we are in, 0..3
// Record the highest argument number in the pattern. (In the
// subpattern {3,number} the argument number is 3.)
int32_t formatNumber = 0;
UBool inQuote = FALSE;
int32_t braceStack = 0;
// Clear error struct
parseError.offset = -1;
parseError.preContext[0] = parseError.postContext[0] = (UChar)0;
int32_t patLen = pattern.length();
int32_t i;
for (i=0; i<subformatCount; ++i) {
delete subformats[i].format;
}
subformatCount = 0;
argTypeCount = 0;
for (i=0; i<patLen; ++i) {
UChar ch = pattern[i];
if (part == 0) {
// In segment 0, recognize and remove quotes
if (ch == SINGLE_QUOTE) {
if (i+1 < patLen && pattern[i+1] == SINGLE_QUOTE) {
segments[0] += ch;
++i;
} else {
inQuote = !inQuote;
}
} else if (ch == LEFT_CURLY_BRACE && !inQuote) {
// The only way we get from segment 0 to 1 is via an
// unquoted '{'.
part = 1;
} else {
segments[0] += ch;
}
} else if (inQuote) {
// In segments 1..3, recognize quoted matter, and copy it
// into the segment, together with the quotes. This takes
// care of '' as well.
segments[part] += ch;
if (ch == SINGLE_QUOTE) {
inQuote = FALSE;
}
} else {
// We have an unquoted character in segment 1..3
switch (ch) {
case COMMA:
// Commas bump us to the next segment, except for segment 3,
// which can contain commas. See example above.
if (part < 3)
part += 1;
else
segments[3] += ch;
break;
case LEFT_CURLY_BRACE:
// Handle '{' within segment 3. The initial '{'
// before segment 1 is handled above.
if (part != 3) {
ec = U_PATTERN_SYNTAX_ERROR;
goto SYNTAX_ERROR;
}
++braceStack;
segments[part] += ch;
break;
case RIGHT_CURLY_BRACE:
if (braceStack == 0) {
makeFormat(formatNumber, segments, parseError,ec);
if (U_FAILURE(ec)){
goto SYNTAX_ERROR;
}
formatNumber++;
segments[1].remove();
segments[2].remove();
segments[3].remove();
part = 0;
} else {
--braceStack;
segments[part] += ch;
}
break;
case SINGLE_QUOTE:
inQuote = TRUE;
// fall through (copy quote chars in segments 1..3)
default:
segments[part] += ch;
break;
}
}
}
if (braceStack != 0 || part != 0) {
// Unmatched braces in the pattern
ec = U_UNMATCHED_BRACES;
goto SYNTAX_ERROR;
}
fPattern = segments[0];
return;
SYNTAX_ERROR:
syntaxError(pattern, i, parseError);
for (i=0; i<subformatCount; ++i) {
delete subformats[i].format;
}
argTypeCount = subformatCount = 0;
}
// -------------------------------------
// Converts this MessageFormat instance to a pattern.
UnicodeString&
MessageFormat::toPattern(UnicodeString& appendTo) const {
// later, make this more extensible
int32_t lastOffset = 0;
int32_t i;
for (i=0; i<subformatCount; ++i) {
copyAndFixQuotes(fPattern, lastOffset, subformats[i].offset, appendTo);
lastOffset = subformats[i].offset;
appendTo += LEFT_CURLY_BRACE;
itos(subformats[i].arg, appendTo);
Format* fmt = subformats[i].format;
if (fmt == NULL) {
// do nothing, string format
}
else if (fmt->getDynamicClassID() == DecimalFormat::getStaticClassID()) {
UErrorCode ec = U_ZERO_ERROR;
NumberFormat& formatAlias = *(NumberFormat*)fmt;
NumberFormat *defaultTemplate = NumberFormat::createInstance(fLocale, ec);
NumberFormat *currencyTemplate = NumberFormat::createCurrencyInstance(fLocale, ec);
NumberFormat *percentTemplate = NumberFormat::createPercentInstance(fLocale, ec);
NumberFormat *integerTemplate = createIntegerFormat(fLocale, ec);
appendTo += COMMA;
appendTo += ID_NUMBER;
if (formatAlias != *defaultTemplate) {
appendTo += COMMA;
if (formatAlias == *currencyTemplate) {
appendTo += ID_CURRENCY;
}
else if (formatAlias == *percentTemplate) {
appendTo += ID_PERCENT;
}
else if (formatAlias == *integerTemplate) {
appendTo += ID_INTEGER;
}
else {
UnicodeString buffer;
appendTo += ((DecimalFormat*)fmt)->toPattern(buffer);
}
}
delete defaultTemplate;
delete currencyTemplate;
delete percentTemplate;
delete integerTemplate;
}
else if (fmt->getDynamicClassID() == SimpleDateFormat::getStaticClassID()) {
DateFormat& formatAlias = *(DateFormat*)fmt;
DateFormat *defaultDateTemplate = DateFormat::createDateInstance(DateFormat::kDefault, fLocale);
DateFormat *shortDateTemplate = DateFormat::createDateInstance(DateFormat::kShort, fLocale);
DateFormat *longDateTemplate = DateFormat::createDateInstance(DateFormat::kLong, fLocale);
DateFormat *fullDateTemplate = DateFormat::createDateInstance(DateFormat::kFull, fLocale);
DateFormat *defaultTimeTemplate = DateFormat::createTimeInstance(DateFormat::kDefault, fLocale);
DateFormat *shortTimeTemplate = DateFormat::createTimeInstance(DateFormat::kShort, fLocale);
DateFormat *longTimeTemplate = DateFormat::createTimeInstance(DateFormat::kLong, fLocale);
DateFormat *fullTimeTemplate = DateFormat::createTimeInstance(DateFormat::kFull, fLocale);
appendTo += COMMA;
if (formatAlias == *defaultDateTemplate) {
appendTo += ID_DATE;
}
else if (formatAlias == *shortDateTemplate) {
appendTo += ID_DATE;
appendTo += COMMA;
appendTo += ID_SHORT;
}
else if (formatAlias == *defaultDateTemplate) {
appendTo += ID_DATE;
appendTo += COMMA;
appendTo += ID_MEDIUM;
}
else if (formatAlias == *longDateTemplate) {
appendTo += ID_DATE;
appendTo += COMMA;
appendTo += ID_LONG;
}
else if (formatAlias == *fullDateTemplate) {
appendTo += ID_DATE;
appendTo += COMMA;
appendTo += ID_FULL;
}
else if (formatAlias == *defaultTimeTemplate) {
appendTo += ID_TIME;
}
else if (formatAlias == *shortTimeTemplate) {
appendTo += ID_TIME;
appendTo += COMMA;
appendTo += ID_SHORT;
}
else if (formatAlias == *defaultTimeTemplate) {
appendTo += ID_TIME;
appendTo += COMMA;
appendTo += ID_MEDIUM;
}
else if (formatAlias == *longTimeTemplate) {
appendTo += ID_TIME;
appendTo += COMMA;
appendTo += ID_LONG;
}
else if (formatAlias == *fullTimeTemplate) {
appendTo += ID_TIME;
appendTo += COMMA;
appendTo += ID_FULL;
}
else {
UnicodeString buffer;
appendTo += ID_DATE;
appendTo += COMMA;
appendTo += ((SimpleDateFormat*)fmt)->toPattern(buffer);
}
delete defaultDateTemplate;
delete shortDateTemplate;
delete longDateTemplate;
delete fullDateTemplate;
delete defaultTimeTemplate;
delete shortTimeTemplate;
delete longTimeTemplate;
delete fullTimeTemplate;
// {sfb} there should be a more efficient way to do this!
}
else if (fmt->getDynamicClassID() == ChoiceFormat::getStaticClassID()) {
UnicodeString buffer;
appendTo += COMMA;
appendTo += ID_CHOICE;
appendTo += COMMA;
appendTo += ((ChoiceFormat*)fmt)->toPattern(buffer);
}
else {
//appendTo += ", unknown";
}
appendTo += RIGHT_CURLY_BRACE;
}
copyAndFixQuotes(fPattern, lastOffset, fPattern.length(), appendTo);
return appendTo;
}
// -------------------------------------
// 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;
}
int32_t i;
if (allocateSubformats(count)) {
for (i=0; i<subformatCount; ++i) {
delete subformats[i].format;
}
for (i=0; i<count; ++i) {
subformats[i].format = newFormats[i];
}
subformatCount = count;
} else {
// An adopt method must always take ownership. Delete
// the incoming format objects and return unchanged.
for (i=0; i<count; ++i) {
delete newFormats[i];
}
}
// TODO: What about the .offset and .arg fields?
}
// -------------------------------------
// 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;
}
if (allocateSubformats(count)) {
int32_t i;
for (i=0; i<subformatCount; ++i) {
delete subformats[i].format;
}
subformatCount = 0;
for (i=0; i<count; ++i) {
subformats[i].format = newFormats[i] ? newFormats[i]->clone() : NULL;
}
subformatCount = count;
}
// TODO: What about the .offset and .arg fields?
}
// -------------------------------------
// Adopt a single format.
// Do nothing is the format number is not less than the array count.
void
MessageFormat::adoptFormat(int32_t n, Format *newFormat) {
if (n < 0 || n >= subformatCount) {
delete newFormat;
} else {
delete subformats[n].format;
subformats[n].format = newFormat;
}
}
// -------------------------------------
// Set a single format.
// Do nothing is the variable is not less than the array count.
void
MessageFormat::setFormat(int32_t n, const Format& newFormat) {
if (n >= 0 && n < subformatCount) {
delete subformats[n].format;
if (&newFormat == NULL) {
// This should never happen -- but we'll be nice if it does
subformats[n].format = NULL;
} else {
subformats[n].format = newFormat.clone();
}
}
}
// -------------------------------------
// 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.
MessageFormat* t = (MessageFormat*) this;
cnt = 0;
if (formatAliases == NULL) {
t->formatAliasesCapacity = (subformatCount<10) ? 10 : subformatCount;
Format** a = (Format**)
uprv_malloc(sizeof(Format*) * formatAliasesCapacity);
if (a == NULL) {
return NULL;
}
t->formatAliases = a;
} else if (subformatCount > formatAliasesCapacity) {
Format** a = (Format**)
uprv_realloc(formatAliases, sizeof(Format*) * subformatCount);
if (a == NULL) {
return NULL;
}
t->formatAliases = a;
t->formatAliasesCapacity = subformatCount;
}
for (int32_t i=0; i<subformatCount; ++i) {
t->formatAliases[i] = subformats[i].format;
}
cnt = subformatCount;
return (const Format**)formatAliases;
}
// -------------------------------------
// 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
{
if (U_FAILURE(success))
return appendTo;
return format(source, cnt, appendTo, ignore, 0, 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);
FieldPosition ignore(0);
temp.format(arguments, cnt, appendTo, ignore, success);
return appendTo;
}
// -------------------------------------
// 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
{
int32_t cnt;
if (U_FAILURE(success))
return appendTo;
if (source.getType() != Formattable::kArray) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
const Formattable* tmpPtr = source.getArray(cnt);
return format(tmpPtr, cnt, appendTo, ignore, 0, success);
}
// -------------------------------------
// Formats the arguments Formattable array and copy into the appendTo buffer.
// Ignore the FieldPosition result for error checking.
UnicodeString&
MessageFormat::format(const Formattable* arguments,
int32_t cnt,
UnicodeString& appendTo,
FieldPosition& status,
int32_t recursionProtection,
UErrorCode& success) const
{
// Allow NULL array only if cnt == 0
if (cnt < 0 || (cnt && arguments == NULL)) {
success = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
int32_t lastOffset = 0;
for (int32_t i=0; i<subformatCount; ++i) {
// Append the prefix of current format element.
appendTo.append(fPattern, lastOffset, subformats[i].offset - lastOffset);
lastOffset = subformats[i].offset;
int32_t argumentNumber = subformats[i].arg;
// Checks the scope of the argument number.
if (argumentNumber >= cnt) {
appendTo += LEFT_CURLY_BRACE;
itos(argumentNumber, appendTo);
appendTo += RIGHT_CURLY_BRACE;
continue;
}
const Formattable *obj = arguments + argumentNumber;
Formattable::Type type = obj->getType();
// Recursively calling the format process only if the current
// format argument refers to a ChoiceFormat object.
Format* fmt = subformats[i].format;
if (fmt != NULL) {
UnicodeString arg;
fmt->format(*obj, arg, success);
// Needs to reprocess the ChoiceFormat option by using the
// MessageFormat pattern application.
if (fmt->getDynamicClassID() == ChoiceFormat::getStaticClassID() &&
arg.indexOf(LEFT_CURLY_BRACE) >= 0) {
MessageFormat temp(arg, fLocale, success);
// TODO: Implement recursion protection
temp.format(arguments, cnt, appendTo, status, recursionProtection, success);
if (U_FAILURE(success)) {
return appendTo;
}
}
else {
appendTo += arg;
}
}
// If the obj data type is a number, use a NumberFormat instance.
else if ((type == Formattable::kDouble) ||
(type == Formattable::kLong) ||
(type == Formattable::kInt64)) {
const NumberFormat* nf = getDefaultNumberFormat(success);
if (nf == NULL) {
return appendTo;
}
if (type == Formattable::kDouble) {
nf->format(obj->getDouble(), appendTo);
} else if (type == Formattable::kLong) {
nf->format(obj->getLong(), appendTo);
} else {
nf->format(obj->getInt64(), appendTo);
}
}
// If the obj data type is a Date instance, use a DateFormat instance.
else if (type == Formattable::kDate) {
const DateFormat* df = getDefaultDateFormat(success);
if (df == NULL) {
return appendTo;
}
df->format(obj->getDate(), appendTo);
}
else if (type == Formattable::kString) {
appendTo += obj->getString();
}
else {
success = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
}
// Appends the rest of the pattern characters after the real last offset.
appendTo.append(fPattern, lastOffset, 0x7fffffff);
return appendTo;
}
// -------------------------------------
// 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
{
// Allocate at least one element. Allocating an array of length
// zero causes problems on some platforms (e.g. Win32).
Formattable *resultArray = new Formattable[argTypeCount ? argTypeCount : 1];
int32_t patternOffset = 0;
int32_t sourceOffset = pos.getIndex();
ParsePosition tempPos(0);
count = 0; // {sfb} reset to zero
int32_t len;
for (int32_t i = 0; i < subformatCount; ++i) {
// match up to format
len = subformats[i].offset - patternOffset;
if (len == 0 ||
fPattern.compare(patternOffset, len, source, sourceOffset, len) == 0) {
sourceOffset += len;
patternOffset += len;
}
else {
goto PARSE_ERROR;
}
// now use format
Format* fmt = subformats[i].format;
int32_t arg = subformats[i].arg;
if (fmt == NULL) { // string format
// if at end, use longest possible match
// otherwise uses first match to intervening string
// does NOT recursively try all possibilities
int32_t tempLength = (i+1<subformatCount) ?
subformats[i+1].offset : fPattern.length();
int32_t next;
if (patternOffset >= tempLength) {
next = source.length();
}
else {
UnicodeString buffer;
fPattern.extract(patternOffset,tempLength - patternOffset, buffer);
next = source.indexOf(buffer, sourceOffset);
}
if (next < 0) {
goto PARSE_ERROR;
}
else {
UnicodeString buffer;
source.extract(sourceOffset,next - sourceOffset, buffer);
UnicodeString strValue = buffer;
UnicodeString temp(LEFT_CURLY_BRACE);
// {sfb} check this later
itos(arg, temp);
temp += RIGHT_CURLY_BRACE;
if (strValue != temp) {
source.extract(sourceOffset,next - sourceOffset, buffer);
resultArray[arg].setString(buffer);
// {sfb} not sure about this
if ((arg + 1) > count) {
count = arg + 1;
}
}
sourceOffset = next;
}
}
else {
tempPos.setIndex(sourceOffset);
fmt->parseObject(source, resultArray[arg], tempPos);
if (tempPos.getIndex() == sourceOffset) {
goto PARSE_ERROR;
}
if ((arg + 1) > count) {
count = arg + 1;
}
sourceOffset = tempPos.getIndex(); // update
}
}
len = fPattern.length() - patternOffset;
if (len == 0 ||
fPattern.compare(patternOffset, len, source, sourceOffset, len) == 0) {
pos.setIndex(sourceOffset + len);
return resultArray;
}
// else fall through...
PARSE_ERROR:
pos.setErrorIndex(sourceOffset);
delete [] resultArray;
count = 0;
return NULL; // leave index as is to signal error
}
// -------------------------------------
// 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
{
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);
if (U_SUCCESS(status)) {
result.releaseBuffer(len);
}
}
}
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 (U_SUCCESS(ec) && defaultRuleSet.length() > 0) {
fmt->setDefaultRuleSet(defaultRuleSet, ec);
if (U_FAILURE(ec)) { // ignore unrecognized default rule set
ec = U_ZERO_ERROR;
}
}
return fmt;
}
/**
* Reads the segments[] array (see applyPattern()) and parses the
* segments[1..3] into a Format* object. Stores the format object in
* the subformats[] array. Updates the argTypes[] array type
* information for the corresponding argument.
*
* @param formatNumber index into subformats[] for this format
* @param segments array of strings with the parsed pattern segments
* @param parseError parse error data (output param)
* @param ec error code
*/
void
MessageFormat::makeFormat(int32_t formatNumber,
UnicodeString* segments,
UParseError& parseError,
UErrorCode& ec) {
if (U_FAILURE(ec)) {
return;
}
// Parse the argument number
int32_t argumentNumber = stou(segments[1]); // always unlocalized!
if (argumentNumber < 0) {
ec = U_INVALID_FORMAT_ERROR;
return;
}
// Parse the format, recording the argument type and creating a
// new Format object (except for string arguments).
Formattable::Type argType;
Format *fmt = NULL;
int32_t typeID, styleID;
DateFormat::EStyle style;
switch (typeID = findKeyword(segments[2], TYPE_IDS)) {
case 0: // string
argType = Formattable::kString;
break;
case 1: // number
argType = Formattable::kDouble;
switch (findKeyword(segments[3], 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
argType = Formattable::kLong;
fmt = createIntegerFormat(fLocale, ec);
break;
default: // pattern
fmt = NumberFormat::createInstance(fLocale, ec);
if (fmt &&
fmt->getDynamicClassID() == DecimalFormat::getStaticClassID()) {
((DecimalFormat*)fmt)->applyPattern(segments[3],parseError,ec);
}
break;
}
break;
case 2: // date
case 3: // time
argType = Formattable::kDate;
styleID = findKeyword(segments[3], DATE_STYLE_IDS);
style = (styleID >= 0) ? DATE_STYLES[styleID] : DateFormat::kDefault;
if (typeID == 2) {
fmt = DateFormat::createDateInstance(style, fLocale);
} else {
fmt = DateFormat::createTimeInstance(style, fLocale);
}
if (styleID < 0 &&
fmt != NULL &&
fmt->getDynamicClassID() == SimpleDateFormat::getStaticClassID()) {
((SimpleDateFormat*)fmt)->applyPattern(segments[3]);
}
break;
case 4: // choice
argType = Formattable::kDouble;
fmt = new ChoiceFormat(segments[3], parseError, ec);
break;
case 5: // spellout
argType = Formattable::kDouble;
fmt = makeRBNF(URBNF_SPELLOUT, fLocale, segments[3], ec);
break;
case 6: // ordinal
argType = Formattable::kDouble;
fmt = makeRBNF(URBNF_ORDINAL, fLocale, segments[3], ec);
break;
case 7: // duration
argType = Formattable::kDouble;
fmt = makeRBNF(URBNF_DURATION, fLocale, segments[3], ec);
break;
default:
argType = Formattable::kString;
ec = U_ILLEGAL_ARGUMENT_ERROR;
break;
}
if (fmt==NULL && argType!=Formattable::kString && U_SUCCESS(ec)) {
ec = U_MEMORY_ALLOCATION_ERROR;
}
if (!allocateSubformats(formatNumber+1) ||
!allocateArgTypes(argumentNumber+1)) {
ec = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(ec)) {
delete fmt;
return;
}
// Parse succeeded; record results in our arrays
subformats[formatNumber].format = fmt;
subformats[formatNumber].offset = segments[0].length();
subformats[formatNumber].arg = argumentNumber;
subformatCount = formatNumber+1;
// Careful here: argumentNumber may in general arrive out of
// sequence, e.g., "There was {2} on {0,date} (see {1,number})."
argTypes[argumentNumber] = argType;
if (argumentNumber+1 > argTypeCount) {
argTypeCount = argumentNumber+1;
}
}
// -------------------------------------
// Finds the string, s, in the string array, list.
int32_t MessageFormat::findKeyword(const UnicodeString& s,
const UChar * const *list)
{
if (s.length() == 0)
return 0; // default
UnicodeString buffer = s;
// Trims the space characters and turns all characters
// in s to lower case.
buffer.trim().toLower("");
for (int32_t i = 0; list[i]; ++i) {
if (!buffer.compare(list[i], u_strlen(list[i]))) {
return i;
}
}
return -1;
}
// -------------------------------------
// Checks the range of the source text to quote the special
// characters, { and ' and copy to target buffer.
void
MessageFormat::copyAndFixQuotes(const UnicodeString& source,
int32_t start,
int32_t end,
UnicodeString& appendTo)
{
UBool gotLB = FALSE;
for (int32_t i = start; i < end; ++i) {
UChar ch = source[i];
if (ch == LEFT_CURLY_BRACE) {
appendTo += SINGLE_QUOTE;
appendTo += LEFT_CURLY_BRACE;
appendTo += SINGLE_QUOTE;
gotLB = TRUE;
}
else if (ch == RIGHT_CURLY_BRACE) {
if(gotLB) {
appendTo += RIGHT_CURLY_BRACE;
gotLB = FALSE;
}
else {
// orig code.
appendTo += SINGLE_QUOTE;
appendTo += RIGHT_CURLY_BRACE;
appendTo += SINGLE_QUOTE;
}
}
else if (ch == SINGLE_QUOTE) {
appendTo += SINGLE_QUOTE;
appendTo += SINGLE_QUOTE;
}
else {
appendTo += ch;
}
}
}
/**
* Convenience method that ought to be in NumberFormat
*/
NumberFormat*
MessageFormat::createIntegerFormat(const Locale& locale, UErrorCode& status) const {
NumberFormat *temp = NumberFormat::createInstance(locale, status);
if (temp->getDynamicClassID() == DecimalFormat::getStaticClassID()) {
DecimalFormat *temp2 = (DecimalFormat*) temp;
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;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof