/* ******************************************************************************* * Copyright (C) 1997-1999, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * * File CHOICFMT.CPP * * Modification History: * * Date Name Description * 02/19/97 aliu Converted from java. * 03/20/97 helena Finished first cut of implementation and got rid * of nextDouble/previousDouble and replaced with * boolean array. * 4/10/97 aliu Clean up. Modified to work on AIX. * 06/04/97 helena Fixed applyPattern(), toPattern() and not to include * wchar.h. * 07/09/97 helena Made ParsePosition into a class. * 08/06/97 nos removed overloaded constructor, fixed 'format(array)' * 07/22/98 stephen JDK 1.2 Sync - removed bool_t array (doubleFlags) * 02/22/99 stephen Removed character literals for EBCDIC safety ******************************************************************************** */ #include "cpputils.h" #include "unicode/choicfmt.h" #include "unicode/numfmt.h" #include "unicode/locid.h" #include "mutex.h" // ***************************************************************************** // class ChoiceFormat // ***************************************************************************** char ChoiceFormat::fgClassID = 0; // Value is irrelevant NumberFormat* ChoiceFormat::fgNumberFormat = 0; // ------------------------------------- // Creates a ChoiceFormat instance based on the pattern. ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern, UErrorCode& status) : fChoiceLimits(0), fChoiceFormats(0), fCount(0) { applyPattern(newPattern, status); } // ------------------------------------- // Creates a ChoiceFormat instance with the limit array and // format strings for each limit. ChoiceFormat::ChoiceFormat(const double* limits, const UnicodeString* formats, int32_t cnt ) : fChoiceLimits(0), fChoiceFormats(0), fCount(0) { setChoices(limits, formats, cnt ); } // ------------------------------------- // copy constructor ChoiceFormat::ChoiceFormat(const ChoiceFormat& that) : fChoiceLimits(0), fChoiceFormats(0) { *this = that; } // ------------------------------------- bool_t ChoiceFormat::operator==(const Format& that) const { if (this == &that) return TRUE; if (this->getDynamicClassID() != that.getDynamicClassID()) return FALSE; // not the same class if (!NumberFormat::operator==(that)) return FALSE; ChoiceFormat& thatAlias = (ChoiceFormat&)that; if (fCount != thatAlias.fCount) return FALSE; // Checks the limits, the corresponding format string and LE or LT flags. // LE means less than and equal to, LT means less than. for (int32_t i = 0; i < fCount; i++) { if ((fChoiceLimits[i] != thatAlias.fChoiceLimits[i]) || (fChoiceFormats[i] != thatAlias.fChoiceFormats[i])) return FALSE; } return TRUE; } // ------------------------------------- // copy constructor const ChoiceFormat& ChoiceFormat::operator=(const ChoiceFormat& that) { if (this != &that) { NumberFormat::operator=(that); fCount = that.fCount; delete [] fChoiceLimits; fChoiceLimits = 0; delete [] fChoiceFormats; fChoiceFormats = 0; fChoiceLimits = new double[fCount]; fChoiceFormats = new UnicodeString[fCount]; uprv_arrayCopy(that.fChoiceLimits, fChoiceLimits, fCount); uprv_arrayCopy(that.fChoiceFormats, fChoiceFormats, fCount); } return *this; } // ------------------------------------- ChoiceFormat::~ChoiceFormat() { delete [] fChoiceLimits; fChoiceLimits = 0; delete [] fChoiceFormats; fChoiceFormats = 0; fCount = 0; } // ------------------------------------- // NumberFormat cache management NumberFormat* ChoiceFormat::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->setMinimumFractionDigits(1); } return theFormat; } void ChoiceFormat::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; } /** * Convert a string to a double value using a default NumberFormat object * which is static (shared by all ChoiceFormat instances). */ double ChoiceFormat::stod(const UnicodeString& string, UErrorCode& status) { // Use a shared global number format to convert a double value to // or string or vice versa. NumberFormat *myFormat = getNumberFormat(status); if(U_FAILURE(status)) return -1; // OK? Formattable result; myFormat->parse(string, result, status); releaseNumberFormat(myFormat); double value = 0.0; if (U_SUCCESS(status)) { switch(result.getType()) { case Formattable::kLong: value = result.getLong(); break; case Formattable::kDouble: value = result.getDouble(); break; } } return value; } // ------------------------------------- /** * Convert a double value to a string using a default NumberFormat object * which is static (shared by all ChoiceFormat instances). */ UnicodeString& ChoiceFormat::dtos(double value, UnicodeString& string, UErrorCode& status) { NumberFormat *myFormat = getNumberFormat(status); if (U_SUCCESS(status)) { FieldPosition fieldPos(0); myFormat->format(value, string, fieldPos); } releaseNumberFormat(myFormat); return string; } // ------------------------------------- // Applies the pattern to this ChoiceFormat instance. void ChoiceFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) { if (U_FAILURE(status)) return; UnicodeString segments[2]; double newChoiceLimits[30]; // current limit UnicodeString newChoiceFormats[30]; // later, use Vectors int32_t count = 0; int32_t part = 0; double startValue = 0; double oldStartValue = uprv_getNaN(); bool_t inQuote = FALSE; for(int i = 0; i < newPattern.length(); ++i) { UChar ch = newPattern[i]; if(ch == 0x0027 /*'\''*/) { // Check for "''" indicating a literal quote if((i+1) < newPattern.length() && newPattern[i+1] == ch) { segments[part] += ch; ++i; } else inQuote = !inQuote; } else if (inQuote) { segments[part] += ch; } else if (ch == 0x003C /*'<'*/ || ch == 0x0023 /*'#'*/ || ch == 0x2264) { if (segments[0] == "") { status = U_ILLEGAL_ARGUMENT_ERROR; return; } UnicodeString tempBuffer = segments[0]; tempBuffer.trim(); UChar posInf = 0x221E; UChar negInf [] = {0x002D /*'-'*/, posInf }; if (tempBuffer == UnicodeString(&posInf, 1, 1)) { startValue = uprv_getInfinity(); } else if (tempBuffer == UnicodeString(negInf, 2, 2)) { startValue = - uprv_getInfinity(); } else { //segments[0].trim(); startValue = stod(tempBuffer, status); if(U_FAILURE(status)) return; } if (ch == 0x003C /*'<'*/ && ! uprv_isInfinite(startValue)) { startValue = nextDouble(startValue); } // {sfb} There is a bug in MSVC 5.0 sp3 -- 0.0 <= NaN ==> TRUE //if (startValue <= oldStartValue) { if (startValue <= oldStartValue && ! uprv_isNaN(oldStartValue)) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } segments[0].remove(); part = 1; } else if (ch == 0x007C /*'|'*/) { newChoiceLimits[count] = startValue; newChoiceFormats[count] = segments[1]; ++count; oldStartValue = startValue; segments[1].remove(); part = 0; } else { segments[part] += ch; } } // clean up last one if (part == 1) { newChoiceLimits[count] = startValue; newChoiceFormats[count] = segments[1]; ++count; } delete [] fChoiceLimits; fChoiceLimits = 0; delete [] fChoiceFormats; fChoiceFormats = 0; fCount = count; fChoiceLimits = new double[fCount]; fChoiceFormats = new UnicodeString[fCount]; uprv_arrayCopy(newChoiceLimits, fChoiceLimits, fCount); uprv_arrayCopy(newChoiceFormats, fChoiceFormats, fCount); } // ------------------------------------- // Reconstruct the original input pattern. UnicodeString& ChoiceFormat::toPattern(UnicodeString& result) const { result.remove(); for (int32_t i = 0; i < fCount; ++i) { if (i != 0) { result += (UChar)0x007C /*'|'*/; } // choose based upon which has less precision // approximate that by choosing the closest one to an integer. // could do better, but it's not worth it. double less = previousDouble(fChoiceLimits[i]); double tryLessOrEqual = uprv_fabs(uprv_IEEEremainder(fChoiceLimits[i], 1.0)); double tryLess = uprv_fabs(uprv_IEEEremainder(less, 1.0)); UErrorCode status = U_ZERO_ERROR; UnicodeString buf; // {sfb} hack to get this to work on MSVC - NaN doesn't behave as it should if (tryLessOrEqual < tryLess && ! (uprv_isNaN(tryLessOrEqual) || uprv_isNaN(tryLess))) { result += dtos(fChoiceLimits[i], buf, status); result += (UChar)0x0023 /*'#'*/; } else { if (uprv_isPositiveInfinity(fChoiceLimits[i])) { result += (UChar32)0x221E; } else if (uprv_isNegativeInfinity(fChoiceLimits[i])) { result += (UChar)0x002D /*'-'*/; result += (UChar32)0x221E; } else { result += dtos(less, buf, status); } result += (UChar)0x003C /*'<'*/; } // Append fChoiceFormats[i], using quotes if there are special characters. // Single quotes themselves must be escaped in either case. UnicodeString text = fChoiceFormats[i]; bool_t needQuote = text.indexOf((UChar)0x003C /*'<'*/) >= 0 || text.indexOf((UChar)0x0023 /*'#'*/) >= 0 || text.indexOf((UChar32)0x2264) >= 0 || text.indexOf((UChar)0x007C /*'|'*/) >= 0; if (needQuote) result += (UChar)0x0027 /*'\''*/; if (text.indexOf((UChar)0x0027 /*'\''*/) < 0) result += text; else { for (int j = 0; j < text.length(); ++j) { UChar c = text[j]; result += c; if (c == 0x0027 /*'\''*/) result += c; } } if (needQuote) result += (UChar)0x0027 /*'\''*/; } return result; } // ------------------------------------- // Adopts the limit and format arrays. void ChoiceFormat::adoptChoices(double *limits, UnicodeString *formats, int32_t cnt ) { if(limits == 0 || formats == 0) return; delete [] fChoiceLimits; fChoiceLimits = 0; delete [] fChoiceFormats; fChoiceFormats = 0; fChoiceLimits = limits; fChoiceFormats = formats; fCount = cnt; } // ------------------------------------- // Sets the limit and format arrays. void ChoiceFormat::setChoices( const double* limits, const UnicodeString* formats, int32_t cnt ) { if(limits == 0 || formats == 0) return; delete [] fChoiceLimits; fChoiceLimits = 0; delete [] fChoiceFormats; fChoiceFormats = 0; // Note that the old arrays are deleted and this owns // the created array. fCount = cnt; fChoiceLimits = new double[fCount]; fChoiceFormats = new UnicodeString[fCount]; uprv_arrayCopy(limits, fChoiceLimits, fCount); uprv_arrayCopy(formats, fChoiceFormats, fCount); } // ------------------------------------- // Gets the limit array. const double* ChoiceFormat::getLimits(int32_t& cnt) const { cnt = fCount; return fChoiceLimits; } // ------------------------------------- // Gets the format array. const UnicodeString* ChoiceFormat::getFormats(int32_t& cnt) const { cnt = fCount; return fChoiceFormats; } // ------------------------------------- // Formats a long number, it's actually formatted as // a double. The returned format string may differ // from the input number because of this. UnicodeString& ChoiceFormat::format(int32_t number, UnicodeString& toAppendTo, FieldPosition& status) const { return format((double) number, toAppendTo, status); } // ------------------------------------- // Formats a double number. UnicodeString& ChoiceFormat::format(double number, UnicodeString& toAppendTo, FieldPosition& status) const { // find the number int32_t i; for (i = 0; i < fCount; ++i) { if (!(number >= fChoiceLimits[i])) { // same as number < fChoiceLimits, except catches NaN break; } } --i; if (i < 0) i = 0; // return either a formatted number, or a string return (toAppendTo += fChoiceFormats[i]); } // ------------------------------------- // Formats an array of objects. Checks if the data type of the objects // to get the right value for formatting. UnicodeString& ChoiceFormat::format(const Formattable* objs, int32_t cnt, UnicodeString& toAppendTo, FieldPosition& pos, UErrorCode& status) const { if(cnt < 0) { status = U_ILLEGAL_ARGUMENT_ERROR; return toAppendTo; } UnicodeString buffer; for (int32_t i = 0; i < cnt; i++) { buffer.remove(); toAppendTo += format((objs[i].getType() == Formattable::kLong) ? objs[i].getLong() : objs[i].getDouble(), buffer, pos); } return toAppendTo; } // ------------------------------------- // Formats an array of objects. Checks if the data type of the objects // to get the right value for formatting. UnicodeString& ChoiceFormat::format(const Formattable& obj, UnicodeString& toAppendTo, FieldPosition& pos, UErrorCode& status) const { return NumberFormat::format(obj, toAppendTo, pos, status); } // ------------------------------------- void ChoiceFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& status) const { // find the best number (defined as the one with the longest parse) int32_t start = status.getIndex(); int32_t furthest = start; double bestNumber = uprv_getNaN(); double tempNumber = 0.0; for (int i = 0; i < fCount; ++i) { UnicodeString tempString = fChoiceFormats[i]; if(text.compareBetween(start, tempString.length(), tempString, 0, tempString.length()) == 0) { status.setIndex(start + tempString.length()); tempNumber = fChoiceLimits[i]; if (status.getIndex() > furthest) { furthest = status.getIndex(); bestNumber = tempNumber; if (furthest == text.length()) break; } } } status.setIndex(furthest); if (status.getIndex() == start) { status.setErrorIndex(furthest); } result.setDouble(bestNumber); } // ------------------------------------- // Parses the text and return the Formattable object. void ChoiceFormat::parse(const UnicodeString& text, Formattable& result, UErrorCode& status) const { NumberFormat::parse(text, result, status); } // ------------------------------------- Format* ChoiceFormat::clone() const { ChoiceFormat *aCopy = new ChoiceFormat(*this); return aCopy; } // ------------------------------------- double ChoiceFormat::nextDouble( double d, bool_t positive ) { return uprv_nextDouble( d, positive ); } //eof