| /* |
| ******************************************************************************* |
| * Copyright (C) 1997-2009, 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 UBool array (doubleFlags) |
| * 02/22/99 stephen Removed character literals for EBCDIC safety |
| ******************************************************************************** |
| */ |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "unicode/choicfmt.h" |
| #include "unicode/numfmt.h" |
| #include "unicode/locid.h" |
| #include "cpputils.h" |
| #include "cstring.h" |
| #include "putilimp.h" |
| #include <stdio.h> |
| #include <float.h> |
| |
| // ***************************************************************************** |
| // class ChoiceFormat |
| // ***************************************************************************** |
| |
| U_NAMESPACE_BEGIN |
| |
| UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChoiceFormat) |
| |
| // Special characters used by ChoiceFormat. There are two characters |
| // used interchangeably to indicate <=. Either is parsed, but only |
| // LESS_EQUAL is generated by toPattern(). |
| #define SINGLE_QUOTE ((UChar)0x0027) /*'*/ |
| #define LESS_THAN ((UChar)0x003C) /*<*/ |
| #define LESS_EQUAL ((UChar)0x0023) /*#*/ |
| #define LESS_EQUAL2 ((UChar)0x2264) |
| #define VERTICAL_BAR ((UChar)0x007C) /*|*/ |
| #define MINUS ((UChar)0x002D) /*-*/ |
| |
| #ifdef INFINITY |
| #undef INFINITY |
| #endif |
| #define INFINITY ((UChar)0x221E) |
| |
| static const UChar gPositiveInfinity[] = {INFINITY, 0}; |
| static const UChar gNegativeInfinity[] = {MINUS, INFINITY, 0}; |
| #define POSITIVE_INF_STRLEN 1 |
| #define NEGATIVE_INF_STRLEN 2 |
| |
| // ------------------------------------- |
| // Creates a ChoiceFormat instance based on the pattern. |
| |
| ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern, |
| UErrorCode& status) |
| : fChoiceLimits(0), |
| fClosures(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), |
| fClosures(0), |
| fChoiceFormats(0), |
| fCount(0) |
| { |
| setChoices(limits, formats, cnt ); |
| } |
| |
| // ------------------------------------- |
| |
| ChoiceFormat::ChoiceFormat(const double* limits, |
| const UBool* closures, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| : fChoiceLimits(0), |
| fClosures(0), |
| fChoiceFormats(0), |
| fCount(0) |
| { |
| setChoices(limits, closures, formats, cnt ); |
| } |
| |
| // ------------------------------------- |
| // copy constructor |
| |
| ChoiceFormat::ChoiceFormat(const ChoiceFormat& that) |
| : NumberFormat(that), |
| fChoiceLimits(0), |
| fClosures(0), |
| fChoiceFormats(0) |
| { |
| *this = that; |
| } |
| |
| // ------------------------------------- |
| // Private constructor that creates a |
| // ChoiceFormat instance based on the |
| // pattern and populates UParseError |
| |
| ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern, |
| UParseError& parseError, |
| UErrorCode& status) |
| : fChoiceLimits(0), |
| fClosures(0), |
| fChoiceFormats(0), |
| fCount(0) |
| { |
| applyPattern(newPattern,parseError, status); |
| } |
| // ------------------------------------- |
| |
| UBool |
| ChoiceFormat::operator==(const Format& that) const |
| { |
| if (this == &that) return TRUE; |
| 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]) || |
| (fClosures[i] != thatAlias.fClosures[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; |
| uprv_free(fChoiceLimits); |
| fChoiceLimits = NULL; |
| uprv_free(fClosures); |
| fClosures = NULL; |
| delete [] fChoiceFormats; |
| fChoiceFormats = NULL; |
| |
| fChoiceLimits = (double*) uprv_malloc( sizeof(double) * fCount); |
| fClosures = (UBool*) uprv_malloc( sizeof(UBool) * fCount); |
| fChoiceFormats = new UnicodeString[fCount]; |
| |
| // check for memory allocation error |
| if (!fChoiceLimits || !fClosures || !fChoiceFormats) { |
| if (fChoiceLimits) { |
| uprv_free(fChoiceLimits); |
| fChoiceLimits = NULL; |
| } |
| if (fClosures) { |
| uprv_free(fClosures); |
| fClosures = NULL; |
| } |
| if (fChoiceFormats) { |
| delete[] fChoiceFormats; |
| fChoiceFormats = NULL; |
| } |
| } else { |
| uprv_arrayCopy(that.fChoiceLimits, fChoiceLimits, fCount); |
| uprv_arrayCopy(that.fClosures, fClosures, fCount); |
| uprv_arrayCopy(that.fChoiceFormats, fChoiceFormats, fCount); |
| } |
| } |
| return *this; |
| } |
| |
| // ------------------------------------- |
| |
| ChoiceFormat::~ChoiceFormat() |
| { |
| uprv_free(fChoiceLimits); |
| fChoiceLimits = NULL; |
| uprv_free(fClosures); |
| fClosures = NULL; |
| delete [] fChoiceFormats; |
| fChoiceFormats = NULL; |
| fCount = 0; |
| } |
| |
| /** |
| * Convert a string to a double value |
| */ |
| double |
| ChoiceFormat::stod(const UnicodeString& string) |
| { |
| char source[256]; |
| char* end; |
| |
| string.extract(0, string.length(), source, (int32_t)sizeof(source), US_INV); /* invariant codepage */ |
| return uprv_strtod(source,&end); |
| } |
| |
| // ------------------------------------- |
| |
| /** |
| * Convert a double value to a string without the overhead of ICU. |
| */ |
| UnicodeString& |
| ChoiceFormat::dtos(double value, |
| UnicodeString& string) |
| { |
| /* Buffer to contain the digits and any extra formatting stuff. */ |
| char temp[DBL_DIG + 16]; |
| char *itrPtr = temp; |
| char *expPtr; |
| |
| sprintf(temp, "%.*g", DBL_DIG, value); |
| |
| /* Find and convert the decimal point. |
| Using setlocale on some machines will cause sprintf to use a comma for certain locales. |
| */ |
| while (*itrPtr && (*itrPtr == '-' || isdigit(*itrPtr))) { |
| itrPtr++; |
| } |
| if (*itrPtr != 0 && *itrPtr != 'e') { |
| /* We reached something that looks like a decimal point. |
| In case someone used setlocale(), which changes the decimal point. */ |
| *itrPtr = '.'; |
| itrPtr++; |
| } |
| /* Search for the exponent */ |
| while (*itrPtr && *itrPtr != 'e') { |
| itrPtr++; |
| } |
| if (*itrPtr == 'e') { |
| itrPtr++; |
| /* Verify the exponent sign */ |
| if (*itrPtr == '+' || *itrPtr == '-') { |
| itrPtr++; |
| } |
| /* Remove leading zeros. You will see this on Windows machines. */ |
| expPtr = itrPtr; |
| while (*itrPtr == '0') { |
| itrPtr++; |
| } |
| if (*itrPtr && expPtr != itrPtr) { |
| /* Shift the exponent without zeros. */ |
| while (*itrPtr) { |
| *(expPtr++) = *(itrPtr++); |
| } |
| // NULL terminate |
| *expPtr = 0; |
| } |
| } |
| |
| string = UnicodeString(temp, -1, US_INV); /* invariant codepage */ |
| return string; |
| } |
| |
| // ------------------------------------- |
| // calls the overloaded applyPattern method. |
| |
| void |
| ChoiceFormat::applyPattern(const UnicodeString& pattern, |
| UErrorCode& status) |
| { |
| UParseError parseError; |
| applyPattern(pattern, parseError, status); |
| } |
| |
| // ------------------------------------- |
| // Applies the pattern to this ChoiceFormat instance. |
| |
| void |
| ChoiceFormat::applyPattern(const UnicodeString& pattern, |
| UParseError& parseError, |
| UErrorCode& status) |
| { |
| if (U_FAILURE(status)) |
| { |
| return; |
| } |
| |
| // Clear error struct |
| parseError.offset = -1; |
| parseError.preContext[0] = parseError.postContext[0] = (UChar)0; |
| |
| // Perform 2 passes. The first computes the number of limits in |
| // this pattern (fCount), which is 1 more than the number of |
| // literal VERTICAL_BAR characters. |
| int32_t count = 1; |
| int32_t i; |
| for (i=0; i<pattern.length(); ++i) { |
| UChar c = pattern[i]; |
| if (c == SINGLE_QUOTE) { |
| // Skip over the entire quote, including embedded |
| // contiguous pairs of SINGLE_QUOTE. |
| for (;;) { |
| do { |
| ++i; |
| } while (i<pattern.length() && |
| pattern[i] != SINGLE_QUOTE); |
| if ((i+1)<pattern.length() && |
| pattern[i+1] == SINGLE_QUOTE) { |
| // SINGLE_QUOTE pair; skip over it |
| ++i; |
| } else { |
| break; |
| } |
| } |
| } else if (c == VERTICAL_BAR) { |
| ++count; |
| } |
| } |
| |
| // Allocate the required storage. |
| double *newLimits = (double*) uprv_malloc( sizeof(double) * count); |
| /* test for NULL */ |
| if (newLimits == 0) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| UBool *newClosures = (UBool*) uprv_malloc( sizeof(UBool) * count); |
| /* test for NULL */ |
| if (newClosures == 0) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| uprv_free(newLimits); |
| return; |
| } |
| UnicodeString *newFormats = new UnicodeString[count]; |
| /* test for NULL */ |
| if (newFormats == 0) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| uprv_free(newLimits); |
| uprv_free(newClosures); |
| return; |
| } |
| |
| // Perform the second pass |
| int32_t k = 0; // index into newXxx[] arrays |
| UnicodeString buf; // scratch buffer |
| UBool inQuote = FALSE; |
| UBool inNumber = TRUE; // TRUE before < or #, FALSE after |
| |
| for (i=0; i<pattern.length(); ++i) { |
| UChar c = pattern[i]; |
| if (c == SINGLE_QUOTE) { |
| // Check for SINGLE_QUOTE pair indicating a literal quote |
| if ((i+1) < pattern.length() && |
| pattern[i+1] == SINGLE_QUOTE) { |
| buf += SINGLE_QUOTE; |
| ++i; |
| } else { |
| inQuote = !inQuote; |
| } |
| } else if (inQuote) { |
| buf += c; |
| } else if (c == LESS_THAN || c == LESS_EQUAL || c == LESS_EQUAL2) { |
| if (!inNumber || buf.length() == 0) { |
| goto error; |
| } |
| inNumber = FALSE; |
| |
| double limit; |
| buf.trim(); |
| if (!buf.compare(gPositiveInfinity, POSITIVE_INF_STRLEN)) { |
| limit = uprv_getInfinity(); |
| } else if (!buf.compare(gNegativeInfinity, NEGATIVE_INF_STRLEN)) { |
| limit = -uprv_getInfinity(); |
| } else { |
| limit = stod(buf); |
| } |
| |
| if (k == count) { |
| // This shouldn't happen. If it does, it means that |
| // the count determined in the first pass did not |
| // match the number of elements found in the second |
| // pass. |
| goto error; |
| } |
| newLimits[k] = limit; |
| newClosures[k] = (c == LESS_THAN); |
| |
| if (k > 0 && limit <= newLimits[k-1]) { |
| // Each limit must be strictly > than the previous |
| // limit. One exception: Two subsequent limits may be |
| // == if the first closure is FALSE and the second |
| // closure is TRUE. This places the limit value in |
| // the second interval. |
| if (!(limit == newLimits[k-1] && |
| !newClosures[k-1] && |
| newClosures[k])) { |
| goto error; |
| } |
| } |
| |
| buf.truncate(0); |
| } else if (c == VERTICAL_BAR) { |
| if (inNumber) { |
| goto error; |
| } |
| inNumber = TRUE; |
| |
| newFormats[k] = buf; |
| ++k; |
| buf.truncate(0); |
| } else { |
| buf += c; |
| } |
| } |
| |
| if (k != (count-1) || inNumber || inQuote) { |
| goto error; |
| } |
| newFormats[k] = buf; |
| |
| // Don't modify this object until the parse succeeds |
| uprv_free(fChoiceLimits); |
| uprv_free(fClosures); |
| delete[] fChoiceFormats; |
| fCount = count; |
| fChoiceLimits = newLimits; |
| fClosures = newClosures; |
| fChoiceFormats = newFormats; |
| return; |
| |
| error: |
| status = U_ILLEGAL_ARGUMENT_ERROR; |
| syntaxError(pattern,i,parseError); |
| uprv_free(newLimits); |
| uprv_free(newClosures); |
| delete[] newFormats; |
| return; |
| |
| } |
| // ------------------------------------- |
| // 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 += VERTICAL_BAR; |
| } |
| UnicodeString buf; |
| if (uprv_isPositiveInfinity(fChoiceLimits[i])) { |
| result += INFINITY; |
| } else if (uprv_isNegativeInfinity(fChoiceLimits[i])) { |
| result += MINUS; |
| result += INFINITY; |
| } else { |
| result += dtos(fChoiceLimits[i], buf); |
| } |
| if (fClosures[i]) { |
| result += LESS_THAN; |
| } else { |
| result += LESS_EQUAL; |
| } |
| // Append fChoiceFormats[i], using quotes if there are special |
| // characters. Single quotes themselves must be escaped in |
| // either case. |
| const UnicodeString& text = fChoiceFormats[i]; |
| UBool needQuote = text.indexOf(LESS_THAN) >= 0 |
| || text.indexOf(LESS_EQUAL) >= 0 |
| || text.indexOf(LESS_EQUAL2) >= 0 |
| || text.indexOf(VERTICAL_BAR) >= 0; |
| if (needQuote) { |
| result += SINGLE_QUOTE; |
| } |
| if (text.indexOf(SINGLE_QUOTE) < 0) { |
| result += text; |
| } |
| else { |
| for (int32_t j = 0; j < text.length(); ++j) { |
| UChar c = text[j]; |
| result += c; |
| if (c == SINGLE_QUOTE) { |
| result += c; |
| } |
| } |
| } |
| if (needQuote) { |
| result += SINGLE_QUOTE; |
| } |
| } |
| |
| return result; |
| } |
| |
| // ------------------------------------- |
| // Sets the limit and format arrays. |
| void |
| ChoiceFormat::setChoices( const double* limits, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| { |
| setChoices(limits, 0, formats, cnt); |
| } |
| |
| // ------------------------------------- |
| // Sets the limit and format arrays. |
| void |
| ChoiceFormat::setChoices( const double* limits, |
| const UBool* closures, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| { |
| if(limits == 0 || formats == 0) |
| return; |
| |
| if (fChoiceLimits) { |
| uprv_free(fChoiceLimits); |
| } |
| if (fClosures) { |
| uprv_free(fClosures); |
| } |
| if (fChoiceFormats) { |
| delete [] fChoiceFormats; |
| } |
| |
| // Note that the old arrays are deleted and this owns |
| // the created array. |
| fCount = cnt; |
| fChoiceLimits = (double*) uprv_malloc( sizeof(double) * fCount); |
| fClosures = (UBool*) uprv_malloc( sizeof(UBool) * fCount); |
| fChoiceFormats = new UnicodeString[fCount]; |
| |
| //check for memory allocation error |
| if (!fChoiceLimits || !fClosures || !fChoiceFormats) { |
| if (fChoiceLimits) { |
| uprv_free(fChoiceLimits); |
| fChoiceLimits = NULL; |
| } |
| if (fClosures) { |
| uprv_free(fClosures); |
| fClosures = NULL; |
| } |
| if (fChoiceFormats) { |
| delete[] fChoiceFormats; |
| fChoiceFormats = NULL; |
| } |
| return; |
| } |
| |
| uprv_arrayCopy(limits, fChoiceLimits, fCount); |
| uprv_arrayCopy(formats, fChoiceFormats, fCount); |
| |
| if (closures != 0) { |
| uprv_arrayCopy(closures, fClosures, fCount); |
| } else { |
| int32_t i; |
| for (i=0; i<fCount; ++i) { |
| fClosures[i] = FALSE; |
| } |
| } |
| } |
| |
| // ------------------------------------- |
| // Gets the limit array. |
| |
| const double* |
| ChoiceFormat::getLimits(int32_t& cnt) const |
| { |
| cnt = fCount; |
| return fChoiceLimits; |
| } |
| |
| // ------------------------------------- |
| // Gets the closures array. |
| |
| const UBool* |
| ChoiceFormat::getClosures(int32_t& cnt) const |
| { |
| cnt = fCount; |
| return fClosures; |
| } |
| |
| // ------------------------------------- |
| // Gets the format array. |
| |
| const UnicodeString* |
| ChoiceFormat::getFormats(int32_t& cnt) const |
| { |
| cnt = fCount; |
| return fChoiceFormats; |
| } |
| |
| // ------------------------------------- |
| // Formats an int64 number, it's actually formatted as |
| // a double. The returned format string may differ |
| // from the input number because of this. |
| |
| UnicodeString& |
| ChoiceFormat::format(int64_t number, |
| UnicodeString& appendTo, |
| FieldPosition& status) const |
| { |
| return format((double) number, appendTo, status); |
| } |
| |
| // ------------------------------------- |
| // 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& appendTo, |
| FieldPosition& status) const |
| { |
| return format((double) number, appendTo, status); |
| } |
| |
| // ------------------------------------- |
| // Formats a double number. |
| |
| UnicodeString& |
| ChoiceFormat::format(double number, |
| UnicodeString& appendTo, |
| FieldPosition& /*pos*/) const |
| { |
| // find the number |
| int32_t i; |
| for (i = 0; i < fCount; ++i) { |
| if (fClosures[i]) { |
| if (!(number > fChoiceLimits[i])) { |
| // same as number <= fChoiceLimits, except catches NaN |
| break; |
| } |
| } else 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 |
| appendTo += fChoiceFormats[i]; |
| return appendTo; |
| } |
| |
| // ------------------------------------- |
| // 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& appendTo, |
| FieldPosition& pos, |
| UErrorCode& status) const |
| { |
| if(cnt < 0) { |
| status = U_ILLEGAL_ARGUMENT_ERROR; |
| return appendTo; |
| } |
| |
| UnicodeString buffer; |
| for (int32_t i = 0; i < cnt; i++) { |
| double objDouble = objs[i].getDouble(status); |
| if (U_SUCCESS(status)) { |
| buffer.remove(); |
| appendTo += format(objDouble, buffer, pos); |
| } |
| } |
| |
| return appendTo; |
| } |
| |
| // ------------------------------------- |
| // 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& appendTo, |
| FieldPosition& pos, |
| UErrorCode& status) const |
| { |
| return NumberFormat::format(obj, appendTo, 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) { |
| int32_t len = fChoiceFormats[i].length(); |
| if (text.compare(start, len, fChoiceFormats[i]) == 0) { |
| status.setIndex(start + len); |
| 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; |
| } |
| |
| U_NAMESPACE_END |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |
| |
| //eof |