diff options
Diffstat (limited to 'Build/source/libs/icu/icu-xetex/i18n/msgfmt.cpp')
-rw-r--r-- | Build/source/libs/icu/icu-xetex/i18n/msgfmt.cpp | 1466 |
1 files changed, 1466 insertions, 0 deletions
diff --git a/Build/source/libs/icu/icu-xetex/i18n/msgfmt.cpp b/Build/source/libs/icu/icu-xetex/i18n/msgfmt.cpp new file mode 100644 index 00000000000..9eb7bcb303a --- /dev/null +++ b/Build/source/libs/icu/icu-xetex/i18n/msgfmt.cpp @@ -0,0 +1,1466 @@ +/* +******************************************************************************* +* Copyright (C) 1997-2006, 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 U_NAMESPACE_QUALIFIER DateFormat::EStyle DATE_STYLES[] = { + U_NAMESPACE_QUALIFIER DateFormat::kDefault, + U_NAMESPACE_QUALIFIER DateFormat::kShort, + U_NAMESPACE_QUALIFIER DateFormat::kMedium, + U_NAMESPACE_QUALIFIER DateFormat::kLong, + U_NAMESPACE_QUALIFIER 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); + result.releaseBuffer(U_SUCCESS(status) ? len : 0); + } + } + if (U_FAILURE(status)) { + result.setToBogus(); + } + return result; +} + +// ------------------------------------- + +static Format* makeRBNF(URBNFRuleSetTag tag, const Locale& locale, const UnicodeString& defaultRuleSet, UErrorCode& ec) { + RuleBasedNumberFormat* fmt = new RuleBasedNumberFormat(tag, locale, ec); + if (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 != NULL && 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 |