70537cf0ca
X-SVN-Rev: 17720
1469 lines
46 KiB
C++
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
|