| /* |
| ******************************************************************************* |
| * 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()) { |
| UErrorCode success = U_ZERO_ERROR; |
| 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 |