5a1369d8bc
X-SVN-Rev: 2139
1140 lines
37 KiB
C++
1140 lines
37 KiB
C++
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 1997-1999, 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/msgfmt.h"
|
|
#include "unicode/decimfmt.h"
|
|
#include "unicode/datefmt.h"
|
|
#include "unicode/smpdtfmt.h"
|
|
#include "unicode/choicfmt.h"
|
|
#include "mutex.h"
|
|
|
|
// *****************************************************************************
|
|
// class MessageFormat
|
|
// *****************************************************************************
|
|
|
|
// -------------------------------------
|
|
char MessageFormat::fgClassID = 0; // Value is irrelevant
|
|
|
|
// This global NumberFormat instance is shared by all MessageFormat to
|
|
// convert a number to(format)/from(parse) a string.
|
|
NumberFormat* MessageFormat::fgNumberFormat = 0;
|
|
|
|
// -------------------------------------
|
|
// Creates a MessageFormat instance based on the pattern.
|
|
|
|
MessageFormat::MessageFormat(const UnicodeString& pattern,
|
|
UErrorCode& success)
|
|
: fOffsets(NULL),
|
|
fArgumentNumbers(NULL),
|
|
fLocale(Locale::getDefault()), // Uses the default locale
|
|
fCount(0)
|
|
{
|
|
fCount = kMaxFormat;
|
|
fOffsets = new int32_t[fCount];
|
|
fArgumentNumbers = new int32_t[fCount];
|
|
for (int32_t i = 0; i < fCount; i++) {
|
|
fFormats[i] = NULL; // Format instances
|
|
fOffsets[i] = 0; // Starting offset
|
|
fArgumentNumbers[i] = 0; // Argument numbers.
|
|
}
|
|
applyPattern(pattern, success);
|
|
}
|
|
|
|
MessageFormat::MessageFormat(const UnicodeString& pattern,
|
|
const Locale& newLocale,
|
|
UErrorCode& success)
|
|
: fOffsets(NULL),
|
|
fArgumentNumbers(NULL),
|
|
fLocale(newLocale), // Uses the default locale
|
|
fCount(0)
|
|
{
|
|
fCount = kMaxFormat;
|
|
fOffsets = new int32_t[fCount];
|
|
fArgumentNumbers = new int32_t[fCount];
|
|
for (int32_t i = 0; i < fCount; i++) {
|
|
fFormats[i] = NULL; // Format instances
|
|
fOffsets[i] = 0; // Starting offset
|
|
fArgumentNumbers[i] = 0; // Argument numbers.
|
|
}
|
|
applyPattern(pattern, success);
|
|
}
|
|
|
|
MessageFormat::~MessageFormat()
|
|
{
|
|
for (int32_t i = 0; i < fCount; i++)
|
|
delete fFormats[i];
|
|
delete [] fOffsets;
|
|
delete [] fArgumentNumbers;
|
|
fCount = 0;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// copy constructor
|
|
|
|
MessageFormat::MessageFormat(const MessageFormat& that)
|
|
: Format(that),
|
|
fOffsets(NULL),
|
|
fCount(that.fCount),
|
|
fLocale(that.fLocale),
|
|
fMaxOffset(that.fMaxOffset),
|
|
fArgumentNumbers(NULL),
|
|
fPattern(that.fPattern)
|
|
{
|
|
fOffsets = new int32_t[fCount];
|
|
fArgumentNumbers = new int32_t[fCount];
|
|
// Sets up the format instance array, offsets and argument numbers.
|
|
for (int32_t i = 0; i < fCount; i++) {
|
|
fFormats[i] = NULL; // init since delete may be called
|
|
if (that.fFormats[i] != NULL) {
|
|
setFormat(i, *(that.fFormats[i]) ); // setFormat clones the format
|
|
}
|
|
fOffsets[i] = that.fOffsets[i];
|
|
fArgumentNumbers[i] = that.fArgumentNumbers[i];
|
|
}
|
|
}
|
|
|
|
// -------------------------------------
|
|
// assignment operator
|
|
|
|
const MessageFormat&
|
|
MessageFormat::operator=(const MessageFormat& that)
|
|
{
|
|
if (this != &that) {
|
|
// Calls the super class for assignment first.
|
|
Format::operator=(that);
|
|
// Cleans up the format array and the offsets, argument numbers.
|
|
for (int32_t j = 0; j < fCount; j++) {
|
|
delete fFormats[j];
|
|
fFormats[j] = NULL;
|
|
}
|
|
delete [] fOffsets; fOffsets = NULL;
|
|
delete [] fArgumentNumbers; fArgumentNumbers = NULL;
|
|
fPattern = that.fPattern;
|
|
fLocale = that.fLocale;
|
|
fCount = that.fCount;
|
|
fMaxOffset = that.fMaxOffset;
|
|
fOffsets = new int32_t[fCount];
|
|
fArgumentNumbers = new int32_t[fCount];
|
|
// Sets up the format instance array, offsets and argument numbers.
|
|
for (int32_t i = 0; i < fCount; i++) {
|
|
if (that.fFormats[i] == NULL) {
|
|
fFormats[i] = NULL;
|
|
}else{
|
|
adoptFormat(i, that.fFormats[i]->clone());
|
|
}
|
|
fOffsets[i] = that.fOffsets[i];
|
|
fArgumentNumbers[i] = that.fArgumentNumbers[i];
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
UBool
|
|
MessageFormat::operator==(const Format& that) const
|
|
{
|
|
if (this == &that) return TRUE;
|
|
// Are the instances derived from the same Format class?
|
|
if (getStaticClassID() != that.getDynamicClassID()) return FALSE; // not the same class
|
|
// Calls the super class for equality check first.
|
|
if (!Format::operator==(that)) return FALSE;
|
|
MessageFormat& thatAlias = (MessageFormat&)that;
|
|
// Checks the pattern, locale and array count of this MessageFormat object.
|
|
if (fMaxOffset != thatAlias.fMaxOffset) return FALSE;
|
|
if (fPattern != thatAlias.fPattern) return FALSE;
|
|
if (fLocale != thatAlias.fLocale) return FALSE;
|
|
if (fCount != thatAlias.fCount) return FALSE;
|
|
// Checks each element in the arrays for equality last.
|
|
for (int32_t i = 0; i < fCount; i++) {
|
|
if ((fFormats[i] != thatAlias.fFormats[i]) ||
|
|
(fOffsets[i] != thatAlias.fOffsets[i]) ||
|
|
(fArgumentNumbers[i] != thatAlias.fArgumentNumbers[i]))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Creates a copy of this MessageFormat, the caller owns the copy.
|
|
|
|
Format*
|
|
MessageFormat::clone() const
|
|
{
|
|
MessageFormat *aCopy = new MessageFormat(*this);
|
|
return aCopy;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Sets the locale of this MessageFormat object to theLocale.
|
|
|
|
void
|
|
MessageFormat::setLocale(const Locale& theLocale)
|
|
{
|
|
fLocale = theLocale;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Gets the locale of this MessageFormat object.
|
|
|
|
const Locale&
|
|
MessageFormat::getLocale() const
|
|
{
|
|
return fLocale;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Applies the new pattern and returns an error if the pattern
|
|
// is not correct.
|
|
// For example, consider the pattern,
|
|
// "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}"
|
|
// The segments would look like the following,
|
|
// segments[0] == "There "
|
|
// segments[1] == "0"
|
|
// segments[2] == "{0,choice,0#are no files|1#is one file|1<are {0,number,integer}"
|
|
// segments[3] == " files"
|
|
|
|
void
|
|
MessageFormat::applyPattern(const UnicodeString& newPattern,
|
|
UErrorCode& success)
|
|
{
|
|
UnicodeString segments[4];
|
|
int32_t part = 0;
|
|
int32_t formatNumber = 0;
|
|
UBool inQuote = FALSE;
|
|
int32_t braceStack = 0;
|
|
fMaxOffset = -1;
|
|
for (int i = 0; i < newPattern.length(); ++i) {
|
|
UChar ch = newPattern[i];
|
|
if (part == 0) {
|
|
if (ch == 0x0027 /*'\''*/) {
|
|
if (i + 1 < newPattern.length()
|
|
&& newPattern[i+1] == 0x0027 /*'\''*/) {
|
|
segments[part] += ch; // handle doubles
|
|
++i;
|
|
} else {
|
|
inQuote = !inQuote;
|
|
}
|
|
} else if (ch == 0x007B /*'{'*/ && !inQuote) {
|
|
part = 1;
|
|
} else {
|
|
segments[part] += ch;
|
|
}
|
|
} else if (inQuote) { // just copy quotes in parts
|
|
segments[part] += ch;
|
|
if (ch == 0x0027 /*'\''*/) {
|
|
inQuote = FALSE;
|
|
}
|
|
} else {
|
|
switch (ch) {
|
|
case 0x002C /*','*/:
|
|
if (part < 3)
|
|
part += 1;
|
|
else
|
|
segments[part] += ch;
|
|
break;
|
|
case 0x007B /*'{'*/:
|
|
++braceStack;
|
|
segments[part] += ch;
|
|
break;
|
|
case 0x007D /*'}'*/:
|
|
if (braceStack == 0) {
|
|
part = 0;
|
|
makeFormat(i, formatNumber, segments, success);
|
|
if(U_FAILURE(success))
|
|
return;
|
|
formatNumber++;
|
|
} else {
|
|
--braceStack;
|
|
segments[part] += ch;
|
|
}
|
|
break;
|
|
case 0x0027 /*'\''*/:
|
|
inQuote = TRUE;
|
|
// fall through, so we keep quotes in other parts
|
|
default:
|
|
segments[part] += ch;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (braceStack == 0 && part != 0) {
|
|
fMaxOffset = -1;
|
|
success = U_INVALID_FORMAT_ERROR;
|
|
return;
|
|
//throw new IllegalArgumentException("Unmatched braces in the pattern.");
|
|
}
|
|
fPattern = segments[0];
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Converts this MessageFormat instance to a pattern.
|
|
UnicodeString&
|
|
MessageFormat::toPattern(UnicodeString& result) const
|
|
{
|
|
// later, make this more extensible
|
|
int32_t lastOffset = 0;
|
|
for (int i = 0; i <= fMaxOffset; ++i) {
|
|
copyAndFixQuotes(fPattern, lastOffset, fOffsets[i], result);
|
|
lastOffset = fOffsets[i];
|
|
result += (UChar)0x007B /*'{'*/;
|
|
// {sfb} check this later
|
|
//result += (UChar) (fArgumentNumbers[i] + '0');
|
|
UnicodeString temp;
|
|
result += itos(fArgumentNumbers[i], temp);
|
|
if (fFormats[i] == NULL) {
|
|
// do nothing, string format
|
|
}
|
|
else if (fFormats[i]->getDynamicClassID() == DecimalFormat::getStaticClassID()) {
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
NumberFormat& formatAlias = *(NumberFormat*)fFormats[i];
|
|
NumberFormat *numberTemplate = NumberFormat::createInstance(fLocale, status);
|
|
NumberFormat *currencyTemplate = NumberFormat::createCurrencyInstance(fLocale, status);
|
|
NumberFormat *percentTemplate = NumberFormat::createPercentInstance(fLocale, status);
|
|
NumberFormat *integerTemplate = createIntegerFormat(fLocale, status);
|
|
|
|
if (formatAlias == *numberTemplate) {
|
|
result += ",number";
|
|
}
|
|
else if (formatAlias == *currencyTemplate) {
|
|
result += ",number,currency";
|
|
}
|
|
else if (formatAlias == *percentTemplate) {
|
|
result += ",number,percent";
|
|
}
|
|
else if (formatAlias == *integerTemplate) {
|
|
result += ",number,integer";
|
|
}
|
|
else {
|
|
UnicodeString buffer;
|
|
result += ",number,";
|
|
result += ((DecimalFormat*)fFormats[i])->toPattern(buffer);
|
|
}
|
|
|
|
delete numberTemplate;
|
|
delete currencyTemplate;
|
|
delete percentTemplate;
|
|
delete integerTemplate;
|
|
}
|
|
else if (fFormats[i]->getDynamicClassID() == SimpleDateFormat::getStaticClassID()) {
|
|
DateFormat& formatAlias = *(DateFormat*)fFormats[i];
|
|
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);
|
|
|
|
|
|
if (formatAlias == *defaultDateTemplate) {
|
|
result += ",date";
|
|
}
|
|
else if (formatAlias == *shortDateTemplate) {
|
|
result += ",date,short";
|
|
}
|
|
else if (formatAlias == *defaultDateTemplate) {
|
|
result += ",date,medium";
|
|
}
|
|
else if (formatAlias == *longDateTemplate) {
|
|
result += ",date,long";
|
|
}
|
|
else if (formatAlias == *fullDateTemplate) {
|
|
result += ",date,full";
|
|
}
|
|
else if (formatAlias == *defaultTimeTemplate) {
|
|
result += ",time";
|
|
}
|
|
else if (formatAlias == *shortTimeTemplate) {
|
|
result += ",time,short";
|
|
}
|
|
else if (formatAlias == *defaultTimeTemplate) {
|
|
result += ",time,medium";
|
|
}
|
|
else if (formatAlias == *longTimeTemplate) {
|
|
result += ",time,long";
|
|
}
|
|
else if (formatAlias == *fullTimeTemplate) {
|
|
result += ",time,full";
|
|
}
|
|
else {
|
|
UnicodeString buffer;
|
|
result += ",date,";
|
|
result += ((SimpleDateFormat*)fFormats[i])->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 (fFormats[i]->getDynamicClassID() == ChoiceFormat::getStaticClassID()) {
|
|
UnicodeString buffer;
|
|
result += ",choice,";
|
|
result += ((ChoiceFormat*)fFormats[i])->toPattern(buffer);
|
|
}
|
|
else {
|
|
//result += ", unknown";
|
|
}
|
|
result += (UChar)0x007D /*'}'*/;
|
|
}
|
|
copyAndFixQuotes(fPattern, lastOffset, fPattern.length(), result);
|
|
return result;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Adopts the new formats array and updates the array count.
|
|
// This MessageFormat instance owns the new formats.
|
|
|
|
void
|
|
MessageFormat::adoptFormats(Format** newFormats,
|
|
int32_t cnt)
|
|
{
|
|
if(newFormats == NULL || cnt < 0)
|
|
return;
|
|
|
|
int32_t i;
|
|
// Cleans up first.
|
|
for (i = 0; i < fCount; i++)
|
|
delete fFormats[i];
|
|
fCount = (cnt > kMaxFormat) ? kMaxFormat : cnt;
|
|
for (i = 0; i < fCount; i++)
|
|
fFormats[i] = newFormats[i];
|
|
for (i = kMaxFormat; i < cnt; i++)
|
|
delete newFormats[i];
|
|
}
|
|
// -------------------------------------
|
|
// 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 cnt)
|
|
{
|
|
if(newFormats == NULL || cnt < 0)
|
|
return;
|
|
|
|
int32_t i;
|
|
// Cleans up first.
|
|
for (i = 0; i < fCount; i++)
|
|
delete fFormats[i];
|
|
fCount = (cnt > kMaxFormat) ? kMaxFormat : cnt;
|
|
for (i = 0; i < fCount; i++)
|
|
if (newFormats[i] == NULL) {
|
|
fFormats[i] = NULL;
|
|
}
|
|
else{
|
|
fFormats[i] = newFormats[i]->clone();
|
|
}
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Adopts the first *variable* formats in the format array.
|
|
// This MessageFormat instance owns the new formats.
|
|
// Do nothing is the variable is not less than the array count.
|
|
|
|
void
|
|
MessageFormat::adoptFormat(int32_t variable, Format *newFormat)
|
|
{
|
|
if(variable < 0)
|
|
return;
|
|
|
|
if (variable < fCount) {
|
|
// Deletes the old formats.
|
|
delete fFormats[variable];
|
|
fFormats[variable] = newFormat;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Sets the first *variable* formats in the format array, this
|
|
// MessageFormat instance makes copies of the new formats.
|
|
// Do nothing is the variable is not less than the array count.
|
|
|
|
void
|
|
MessageFormat::setFormat(int32_t variable, const Format& newFormat)
|
|
{
|
|
if (variable < fCount) {
|
|
delete fFormats[variable];
|
|
if (&(newFormat) == NULL) {
|
|
fFormats[variable] = NULL;
|
|
}
|
|
else{
|
|
fFormats[variable] = newFormat.clone();
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Gets the format array.
|
|
|
|
const Format**
|
|
MessageFormat::getFormats(int32_t& cnt) const
|
|
{
|
|
cnt = fCount;
|
|
return (const Format**)fFormats;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// 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& result,
|
|
FieldPosition& ignore,
|
|
UErrorCode& success) const
|
|
{
|
|
if (U_FAILURE(success))
|
|
return result;
|
|
|
|
return format(source, cnt, result, ignore, 0, success);
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Internally creates a MessageFormat instance based on the
|
|
// pattern and formats the arguments Formattable array and
|
|
// copy into the result buffer.
|
|
|
|
UnicodeString&
|
|
MessageFormat::format( const UnicodeString& pattern,
|
|
const Formattable* arguments,
|
|
int32_t cnt,
|
|
UnicodeString& result,
|
|
UErrorCode& success)
|
|
{
|
|
// {sfb} why does this use a local when so many other places use a static?
|
|
MessageFormat *temp = new MessageFormat(pattern, success);
|
|
if (U_FAILURE(success))
|
|
return result;
|
|
FieldPosition ignore(0);
|
|
temp->format(arguments, cnt, result, ignore, success);
|
|
delete temp;
|
|
return result;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Formats the source Formattable object and copy into the
|
|
// result buffer. The Formattable object must be an array
|
|
// of Formattable instances, returns error otherwise.
|
|
|
|
UnicodeString&
|
|
MessageFormat::format(const Formattable& source,
|
|
UnicodeString& result,
|
|
FieldPosition& ignore,
|
|
UErrorCode& success) const
|
|
{
|
|
int32_t cnt;
|
|
|
|
if (U_FAILURE(success))
|
|
return result;
|
|
if (source.getType() != Formattable::kArray) {
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
const Formattable* tmpPtr = source.getArray(cnt);
|
|
|
|
return format(tmpPtr, cnt, result, ignore, 0, success);
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Formats the arguments Formattable array and copy into the result buffer.
|
|
// Ignore the FieldPosition result for error checking.
|
|
|
|
UnicodeString&
|
|
MessageFormat::format(const Formattable* arguments,
|
|
int32_t cnt,
|
|
UnicodeString& result,
|
|
FieldPosition& status,
|
|
int32_t recursionProtection,
|
|
UErrorCode& success) const
|
|
{
|
|
if(/*arguments == NULL ||*/ cnt < 0) {
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
|
|
UnicodeString buffer;
|
|
|
|
int32_t lastOffset = 0;
|
|
for (int32_t i = 0; i <= fMaxOffset;++i) {
|
|
// Cleans up the temp buffer for each formattable arguments.
|
|
buffer.remove();
|
|
// Append the prefix of current format element.
|
|
fPattern.extract(lastOffset, fOffsets[i] - lastOffset, buffer);
|
|
result += buffer;
|
|
lastOffset = fOffsets[i];
|
|
int32_t argumentNumber = fArgumentNumbers[i];
|
|
// Checks the scope of the argument number.
|
|
if (argumentNumber >= cnt) {
|
|
/*success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;*/
|
|
result += "{";
|
|
UnicodeString temp;
|
|
result += itos(argumentNumber, temp);
|
|
result += "}";
|
|
continue;
|
|
}
|
|
|
|
Formattable obj = arguments[argumentNumber];
|
|
UnicodeString arg;
|
|
UBool tryRecursion = FALSE;
|
|
// Recursively calling the format process only if the current format argument
|
|
// refers to a ChoiceFormat object.
|
|
if (fFormats[i] != NULL) {
|
|
fFormats[i]->format(obj, arg, success);
|
|
tryRecursion = (fFormats[i]->getDynamicClassID() == ChoiceFormat::getStaticClassID());
|
|
}
|
|
// If the obj data type if a number, use a NumberFormat instance.
|
|
else if ((obj.getType() == Formattable::kDouble) ||
|
|
(obj.getType() == Formattable::kLong)) {
|
|
NumberFormat *numTemplate = NULL;
|
|
numTemplate = NumberFormat::createInstance(fLocale, success);
|
|
if (U_FAILURE(success)) {
|
|
delete numTemplate;
|
|
return result;
|
|
}
|
|
numTemplate->format((obj.getType() == Formattable::kDouble) ? obj.getDouble() : obj.getLong(), arg);
|
|
delete numTemplate;
|
|
if (U_FAILURE(success))
|
|
return result;
|
|
}
|
|
// If the obj data type is a Date instance, use a DateFormat instance.
|
|
else if (obj.getType() == Formattable::kDate) {
|
|
DateFormat *dateTemplate = NULL;
|
|
dateTemplate = DateFormat::createDateTimeInstance(DateFormat::kShort, DateFormat::kShort, fLocale);
|
|
dateTemplate->format(obj.getDate(), arg);
|
|
delete dateTemplate;
|
|
}
|
|
else if (obj.getType() == Formattable::kString) {
|
|
obj.getString(arg);
|
|
}
|
|
else {
|
|
#ifdef LIUDEBUG
|
|
cerr << "Unknown object of type:" << obj.getType() << endl;
|
|
#endif
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
// Needs to reprocess the ChoiceFormat option by using the MessageFormat
|
|
// pattern application.
|
|
if (tryRecursion && arg.indexOf("{") >= 0) {
|
|
MessageFormat *temp = NULL;
|
|
temp = new MessageFormat(arg, fLocale, success);
|
|
if (U_FAILURE(success))
|
|
return result;
|
|
temp->format(arguments, cnt, result, status, recursionProtection, success);
|
|
if (U_FAILURE(success)) {
|
|
delete temp;
|
|
return result;
|
|
}
|
|
delete temp;
|
|
}
|
|
else {
|
|
result += arg;
|
|
}
|
|
}
|
|
buffer.remove();
|
|
// Appends the rest of the pattern characters after the real last offset.
|
|
fPattern.extract(lastOffset, fPattern.length(), buffer);
|
|
result += buffer;
|
|
return result;
|
|
}
|
|
|
|
// MessageFormat Type List Number, Date, Time or Choice
|
|
const UnicodeString MessageFormat::fgTypeList[] = {
|
|
UnicodeString(), UnicodeString(), UNICODE_STRING("number", 6), UnicodeString(),
|
|
UNICODE_STRING("date", 4), UnicodeString(), UNICODE_STRING("time", 4), UnicodeString(),
|
|
UNICODE_STRING("choice", 6)
|
|
};
|
|
|
|
// NumberFormat modifier list, default, currency, percent or integer
|
|
const UnicodeString MessageFormat::fgModifierList[] = {
|
|
UnicodeString(), UnicodeString(), UNICODE_STRING("currency", 8), UnicodeString(),
|
|
UNICODE_STRING("percent", 7), UnicodeString(), UNICODE_STRING("integer", 7), UnicodeString(),
|
|
UnicodeString()
|
|
};
|
|
|
|
// DateFormat modifier list, default, short, medium, long or full
|
|
const UnicodeString MessageFormat::fgDateModifierList[] = {
|
|
UnicodeString(), UnicodeString(), UNICODE_STRING("short", 5), UnicodeString(),
|
|
UNICODE_STRING("medium", 6), UnicodeString(), UNICODE_STRING("long", 4), UnicodeString(),
|
|
UNICODE_STRING("full", 4)
|
|
};
|
|
|
|
const int32_t MessageFormat::fgListLength= 9;
|
|
|
|
// -------------------------------------
|
|
// 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& status,
|
|
int32_t& count) const
|
|
{
|
|
Formattable *resultArray = new Formattable[kMaxFormat];
|
|
int32_t patternOffset = 0;
|
|
int32_t sourceOffset = status.getIndex();
|
|
ParsePosition tempStatus(0);
|
|
count = 0; // {sfb} reset to zero
|
|
for (int32_t i = 0; i <= fMaxOffset; ++i) {
|
|
// match up to format
|
|
int32_t len = fOffsets[i] - patternOffset;
|
|
if (len == 0 ||
|
|
fPattern.compare(patternOffset, len, source, sourceOffset, len) == 0) {
|
|
sourceOffset += len;
|
|
patternOffset += len;
|
|
}
|
|
else {
|
|
status.setErrorIndex(sourceOffset);
|
|
delete [] resultArray;
|
|
count = 0;
|
|
return NULL; // leave index as is to signal error
|
|
}
|
|
|
|
// now use format
|
|
if (fFormats[i] == 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 != fMaxOffset) ? fOffsets[i+1] : 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) {
|
|
status.setErrorIndex(sourceOffset);
|
|
delete [] resultArray;
|
|
count = 0;
|
|
return NULL; // leave index as is to signal error
|
|
}
|
|
else {
|
|
UnicodeString buffer;
|
|
source.extract(sourceOffset,next - sourceOffset, buffer);
|
|
UnicodeString strValue = buffer;
|
|
UnicodeString temp("{");
|
|
// {sfb} check this later
|
|
UnicodeString temp1;
|
|
temp += itos(fArgumentNumbers[i], temp1);
|
|
temp += "}";
|
|
if (strValue != temp) {
|
|
source.extract(sourceOffset,next - sourceOffset, buffer);
|
|
resultArray[fArgumentNumbers[i]].setString(buffer);
|
|
// {sfb} not sure about this
|
|
if ((fArgumentNumbers[i] + 1) > count)
|
|
count = (fArgumentNumbers[i] + 1);
|
|
}
|
|
sourceOffset = next;
|
|
}
|
|
}
|
|
else {
|
|
tempStatus.setIndex(sourceOffset);
|
|
fFormats[i]->parseObject(source, resultArray[fArgumentNumbers[i]], tempStatus);
|
|
if (tempStatus.getIndex() == sourceOffset) {
|
|
status.setErrorIndex(sourceOffset);
|
|
delete [] resultArray;
|
|
count = 0;
|
|
return NULL; // leave index as is to signal error
|
|
}
|
|
|
|
if ((fArgumentNumbers[i] + 1) > count)
|
|
count = (fArgumentNumbers[i] + 1);
|
|
|
|
sourceOffset = tempStatus.getIndex(); // update
|
|
}
|
|
}
|
|
int32_t len = fPattern.length() - patternOffset;
|
|
if (len == 0 ||
|
|
fPattern.compare(patternOffset, len, source, sourceOffset, len) == 0) {
|
|
status.setIndex(sourceOffset + len);
|
|
}
|
|
else {
|
|
status.setErrorIndex(sourceOffset);
|
|
delete [] resultArray;
|
|
count = 0;
|
|
return NULL; // leave index as is to signal error
|
|
}
|
|
|
|
return resultArray;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// 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;
|
|
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);
|
|
}
|
|
|
|
// -------------------------------------
|
|
// NumberFormat cache management
|
|
|
|
NumberFormat*
|
|
MessageFormat::getNumberFormat(UErrorCode &status)
|
|
{
|
|
NumberFormat *theFormat = 0;
|
|
|
|
if (fgNumberFormat != 0) // if there's something in the cache
|
|
{
|
|
Mutex lock;
|
|
|
|
if (fgNumberFormat != 0) // Someone might have grabbed it.
|
|
{
|
|
theFormat = fgNumberFormat;
|
|
fgNumberFormat = 0; // We have exclusive right to this formatter.
|
|
}
|
|
}
|
|
|
|
if(theFormat == 0) // If we weren't able to pull it out of the cache, then we have to create it.
|
|
{
|
|
theFormat = NumberFormat::createInstance(Locale::US, status);
|
|
if(U_FAILURE(status))
|
|
return 0;
|
|
theFormat->setParseIntegerOnly(TRUE);
|
|
}
|
|
|
|
return theFormat;
|
|
}
|
|
|
|
void
|
|
MessageFormat::releaseNumberFormat(NumberFormat *adopt)
|
|
{
|
|
if(fgNumberFormat == 0) // If the cache is empty we must add it back.
|
|
{
|
|
Mutex lock;
|
|
|
|
if(fgNumberFormat == 0)
|
|
{
|
|
fgNumberFormat = adopt;
|
|
adopt = 0;
|
|
}
|
|
}
|
|
|
|
delete adopt;
|
|
}
|
|
|
|
|
|
/**
|
|
* Converts a string to an integer value using a default NumberFormat object
|
|
* which is static (shared by all MessageFormat instances). This replaces
|
|
* a call to wtoi().
|
|
*/
|
|
int32_t
|
|
MessageFormat::stoi(const UnicodeString& string,
|
|
UErrorCode& status)
|
|
{
|
|
NumberFormat *myFormat = getNumberFormat(status);
|
|
|
|
if(U_FAILURE(status))
|
|
return -1; // OK?
|
|
|
|
Formattable result;
|
|
// Uses the global number formatter to parse the string.
|
|
// Note: We assume here that parse() is thread-safe.
|
|
myFormat->parse(string, result, status);
|
|
releaseNumberFormat(myFormat);
|
|
|
|
int32_t value = 0;
|
|
if (U_SUCCESS(status) && result.getType() == Formattable::kLong)
|
|
value = result.getLong();
|
|
|
|
|
|
return value;
|
|
}
|
|
|
|
// -------------------------------------
|
|
|
|
/**
|
|
* Converts an integer value to a string using a default NumberFormat object
|
|
* which is static (shared by all MessageFormat instances). This replaces
|
|
* a call to wtoi().
|
|
*/
|
|
UnicodeString&
|
|
MessageFormat::itos(int32_t i,
|
|
UnicodeString& string)
|
|
{
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
NumberFormat *myFormat = getNumberFormat(status);
|
|
|
|
if(U_FAILURE(status))
|
|
return (string = "<ERROR>"); // _REVISIT_ maybe toPattern should take an errorcode.
|
|
|
|
UnicodeString &retval = myFormat->format(i, string);
|
|
|
|
releaseNumberFormat(myFormat);
|
|
|
|
return retval;
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Checks which format instance we are really using based on the segments.
|
|
|
|
void
|
|
MessageFormat::makeFormat(int32_t position,
|
|
int32_t offsetNumber,
|
|
UnicodeString* segments,
|
|
UErrorCode& success)
|
|
{
|
|
if(U_FAILURE(success))
|
|
return;
|
|
|
|
// get the number
|
|
int32_t argumentNumber;
|
|
int32_t oldMaxOffset = fMaxOffset;
|
|
argumentNumber = stoi(segments[1], success); // always unlocalized!
|
|
if (argumentNumber < 0 || argumentNumber > 9) {
|
|
success = U_INVALID_FORMAT_ERROR;
|
|
return;
|
|
}
|
|
fMaxOffset = offsetNumber;
|
|
fOffsets[offsetNumber] = segments[0].length();
|
|
fArgumentNumbers[offsetNumber] = argumentNumber;
|
|
|
|
// now get the format
|
|
Format *newFormat = NULL;
|
|
switch (findKeyword(segments[2], fgTypeList)) {
|
|
case 0:
|
|
break;
|
|
case 1: case 2:// number
|
|
switch (findKeyword(segments[3], fgModifierList)) {
|
|
case 0: // default;
|
|
newFormat = NumberFormat::createInstance(fLocale, success);
|
|
break;
|
|
case 1: case 2:// currency
|
|
newFormat = NumberFormat::createCurrencyInstance(fLocale, success);
|
|
break;
|
|
case 3: case 4:// percent
|
|
newFormat = NumberFormat::createPercentInstance(fLocale, success);
|
|
break;
|
|
case 5: case 6:// integer
|
|
newFormat = createIntegerFormat(fLocale, success);
|
|
break;
|
|
default: // pattern
|
|
newFormat = NumberFormat::createInstance(fLocale, success);
|
|
if(U_FAILURE(success)) {
|
|
newFormat = NULL;
|
|
return;
|
|
}
|
|
if(newFormat->getDynamicClassID() == DecimalFormat::getStaticClassID())
|
|
((DecimalFormat*)newFormat)->applyPattern(segments[3], success);
|
|
if(U_FAILURE(success)) {
|
|
fMaxOffset = oldMaxOffset;
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 3: case 4: // date
|
|
switch (findKeyword(segments[3], fgDateModifierList)) {
|
|
case 0: // default
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kDefault, fLocale);
|
|
break;
|
|
case 1: case 2: // short
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kShort, fLocale);
|
|
break;
|
|
case 3: case 4: // medium
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kDefault, fLocale);
|
|
break;
|
|
case 5: case 6: // long
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kLong, fLocale);
|
|
break;
|
|
case 7: case 8: // full
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kFull, fLocale);
|
|
break;
|
|
default:
|
|
newFormat = DateFormat::createDateInstance(DateFormat::kDefault, fLocale);
|
|
if(newFormat->getDynamicClassID() == SimpleDateFormat::getStaticClassID())
|
|
((SimpleDateFormat*)newFormat)->applyPattern(segments[3]);
|
|
if(U_FAILURE(success)) {
|
|
fMaxOffset = oldMaxOffset;
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 5: case 6:// time
|
|
switch (findKeyword(segments[3], fgDateModifierList)) {
|
|
case 0: // default
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kDefault, fLocale);
|
|
break;
|
|
case 1: case 2: // short
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kShort, fLocale);
|
|
break;
|
|
case 3: case 4: // medium
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kDefault, fLocale);
|
|
break;
|
|
case 5: case 6: // long
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kLong, fLocale);
|
|
break;
|
|
case 7: case 8: // full
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kFull, fLocale);
|
|
break;
|
|
default:
|
|
newFormat = DateFormat::createTimeInstance(DateFormat::kDefault, fLocale);
|
|
if(newFormat->getDynamicClassID() == SimpleDateFormat::getStaticClassID())
|
|
((SimpleDateFormat*)newFormat)->applyPattern(segments[3]);
|
|
if(U_FAILURE(success)) {
|
|
fMaxOffset = oldMaxOffset;
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 7: case 8:// choice
|
|
newFormat = new ChoiceFormat(segments[3], success);
|
|
if(U_FAILURE(success)) {
|
|
fMaxOffset = oldMaxOffset;
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
fMaxOffset = oldMaxOffset;
|
|
success = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
if(newFormat != NULL) {
|
|
delete fFormats[offsetNumber];
|
|
fFormats[offsetNumber] = newFormat;
|
|
}
|
|
segments[1].remove(); // throw away other segments
|
|
segments[2].remove();
|
|
segments[3].remove();
|
|
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Finds the string, s, in the string array, list.
|
|
int32_t MessageFormat::findKeyword(const UnicodeString& s,
|
|
const UnicodeString* list)
|
|
{
|
|
UnicodeString buffer = s;
|
|
// Trims the space characters and turns all characters
|
|
// in s to lower case.
|
|
buffer.trim().toLower();
|
|
for (int32_t i = 0; i < fgListLength; ++i) {
|
|
if (buffer == 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& target)
|
|
{
|
|
UBool gotLB = FALSE;
|
|
|
|
for (UTextOffset i = start; i < end; ++i) {
|
|
UChar ch = source[i];
|
|
if (ch == 0x007B /*'{'*/) {
|
|
target += "'{'";
|
|
gotLB = TRUE;
|
|
}
|
|
else if (ch == 0x007D /*'}'*/) {
|
|
if(gotLB) {
|
|
target += "}";
|
|
gotLB = FALSE;
|
|
}
|
|
else
|
|
// orig code.
|
|
target += "'}'";
|
|
}
|
|
else if (ch == 0x0027 /*'\''*/) {
|
|
target += "''";
|
|
}
|
|
else {
|
|
target += 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;
|
|
}
|
|
|
|
//eof
|