| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 1997-2013, 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 |
| |
| #if defined(STARBOARD) |
| #include "starboard/client_porting/poem/assert_poem.h" |
| #include "starboard/client_porting/poem/stdio_poem.h" |
| #endif // defined(STARBOARD) |
| #include "unicode/choicfmt.h" |
| #include "unicode/numfmt.h" |
| #include "unicode/locid.h" |
| #include "cpputils.h" |
| #include "cstring.h" |
| #include "messageimpl.h" |
| #include "putilimp.h" |
| #include "uassert.h" |
| #if !defined(STARBOARD) |
| #include <stdio.h> |
| #include <float.h> |
| #endif |
| |
| // ***************************************************************************** |
| // 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) /*-*/ |
| |
| static const UChar LEFT_CURLY_BRACE = 0x7B; /*{*/ |
| static const UChar RIGHT_CURLY_BRACE = 0x7D; /*}*/ |
| |
| #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) |
| : constructorErrorCode(status), |
| msgPattern(status) |
| { |
| 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 ) |
| : constructorErrorCode(U_ZERO_ERROR), |
| msgPattern(constructorErrorCode) |
| { |
| setChoices(limits, NULL, formats, cnt, constructorErrorCode); |
| } |
| |
| // ------------------------------------- |
| |
| ChoiceFormat::ChoiceFormat(const double* limits, |
| const UBool* closures, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| : constructorErrorCode(U_ZERO_ERROR), |
| msgPattern(constructorErrorCode) |
| { |
| setChoices(limits, closures, formats, cnt, constructorErrorCode); |
| } |
| |
| // ------------------------------------- |
| // copy constructor |
| |
| ChoiceFormat::ChoiceFormat(const ChoiceFormat& that) |
| : NumberFormat(that), |
| constructorErrorCode(that.constructorErrorCode), |
| msgPattern(that.msgPattern) |
| { |
| } |
| |
| // ------------------------------------- |
| // Private constructor that creates a |
| // ChoiceFormat instance based on the |
| // pattern and populates UParseError |
| |
| ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern, |
| UParseError& parseError, |
| UErrorCode& status) |
| : constructorErrorCode(status), |
| msgPattern(status) |
| { |
| 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; |
| return msgPattern == thatAlias.msgPattern; |
| } |
| |
| // ------------------------------------- |
| // copy constructor |
| |
| const ChoiceFormat& |
| ChoiceFormat::operator=(const ChoiceFormat& that) |
| { |
| if (this != &that) { |
| NumberFormat::operator=(that); |
| constructorErrorCode = that.constructorErrorCode; |
| msgPattern = that.msgPattern; |
| } |
| return *this; |
| } |
| |
| // ------------------------------------- |
| |
| ChoiceFormat::~ChoiceFormat() |
| { |
| } |
| |
| // ------------------------------------- |
| |
| /** |
| * Convert a double value to a string without the overhead of NumberFormat. |
| */ |
| 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) |
| { |
| msgPattern.parseChoiceStyle(pattern, NULL, status); |
| constructorErrorCode = status; |
| } |
| |
| // ------------------------------------- |
| // Applies the pattern to this ChoiceFormat instance. |
| |
| void |
| ChoiceFormat::applyPattern(const UnicodeString& pattern, |
| UParseError& parseError, |
| UErrorCode& status) |
| { |
| msgPattern.parseChoiceStyle(pattern, &parseError, status); |
| constructorErrorCode = status; |
| } |
| // ------------------------------------- |
| // Returns the input pattern string. |
| |
| UnicodeString& |
| ChoiceFormat::toPattern(UnicodeString& result) const |
| { |
| return result = msgPattern.getPatternString(); |
| } |
| |
| // ------------------------------------- |
| // Sets the limit and format arrays. |
| void |
| ChoiceFormat::setChoices( const double* limits, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| { |
| UErrorCode errorCode = U_ZERO_ERROR; |
| setChoices(limits, NULL, formats, cnt, errorCode); |
| } |
| |
| // ------------------------------------- |
| // Sets the limit and format arrays. |
| void |
| ChoiceFormat::setChoices( const double* limits, |
| const UBool* closures, |
| const UnicodeString* formats, |
| int32_t cnt ) |
| { |
| UErrorCode errorCode = U_ZERO_ERROR; |
| setChoices(limits, closures, formats, cnt, errorCode); |
| } |
| |
| void |
| ChoiceFormat::setChoices(const double* limits, |
| const UBool* closures, |
| const UnicodeString* formats, |
| int32_t count, |
| UErrorCode &errorCode) { |
| if (U_FAILURE(errorCode)) { |
| return; |
| } |
| if (limits == NULL || formats == NULL) { |
| errorCode = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| // Reconstruct the original input pattern. |
| // Modified version of the pre-ICU 4.8 toPattern() implementation. |
| UnicodeString result; |
| for (int32_t i = 0; i < count; ++i) { |
| if (i != 0) { |
| result += VERTICAL_BAR; |
| } |
| UnicodeString buf; |
| if (uprv_isPositiveInfinity(limits[i])) { |
| result += INFINITY; |
| } else if (uprv_isNegativeInfinity(limits[i])) { |
| result += MINUS; |
| result += INFINITY; |
| } else { |
| result += dtos(limits[i], buf); |
| } |
| if (closures != NULL && closures[i]) { |
| result += LESS_THAN; |
| } else { |
| result += LESS_EQUAL; |
| } |
| // Append formats[i], using quotes if there are special |
| // characters. Single quotes themselves must be escaped in |
| // either case. |
| const UnicodeString& text = formats[i]; |
| int32_t textLength = text.length(); |
| int32_t nestingLevel = 0; |
| for (int32_t j = 0; j < textLength; ++j) { |
| UChar c = text[j]; |
| if (c == SINGLE_QUOTE && nestingLevel == 0) { |
| // Double each top-level apostrophe. |
| result.append(c); |
| } else if (c == VERTICAL_BAR && nestingLevel == 0) { |
| // Surround each pipe symbol with apostrophes for quoting. |
| // If the next character is an apostrophe, then that will be doubled, |
| // and although the parser will see the apostrophe pairs beginning |
| // and ending one character earlier than our doubling, the result |
| // is as desired. |
| // | -> '|' |
| // |' -> '|''' |
| // |'' -> '|''''' etc. |
| result.append(SINGLE_QUOTE).append(c).append(SINGLE_QUOTE); |
| continue; // Skip the append(c) at the end of the loop body. |
| } else if (c == LEFT_CURLY_BRACE) { |
| ++nestingLevel; |
| } else if (c == RIGHT_CURLY_BRACE && nestingLevel > 0) { |
| --nestingLevel; |
| } |
| result.append(c); |
| } |
| } |
| // Apply the reconstructed pattern. |
| applyPattern(result, errorCode); |
| } |
| |
| // ------------------------------------- |
| // Gets the limit array. |
| |
| const double* |
| ChoiceFormat::getLimits(int32_t& cnt) const |
| { |
| cnt = 0; |
| return NULL; |
| } |
| |
| // ------------------------------------- |
| // Gets the closures array. |
| |
| const UBool* |
| ChoiceFormat::getClosures(int32_t& cnt) const |
| { |
| cnt = 0; |
| return NULL; |
| } |
| |
| // ------------------------------------- |
| // Gets the format array. |
| |
| const UnicodeString* |
| ChoiceFormat::getFormats(int32_t& cnt) const |
| { |
| cnt = 0; |
| return NULL; |
| } |
| |
| // ------------------------------------- |
| // 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 an int32_t number, it's actually formatted as |
| // a double. |
| |
| 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 |
| { |
| if (msgPattern.countParts() == 0) { |
| // No pattern was applied, or it failed. |
| return appendTo; |
| } |
| // Get the appropriate sub-message. |
| int32_t msgStart = findSubMessage(msgPattern, 0, number); |
| if (!MessageImpl::jdkAposMode(msgPattern)) { |
| int32_t patternStart = msgPattern.getPart(msgStart).getLimit(); |
| int32_t msgLimit = msgPattern.getLimitPartIndex(msgStart); |
| appendTo.append(msgPattern.getPatternString(), |
| patternStart, |
| msgPattern.getPatternIndex(msgLimit) - patternStart); |
| return appendTo; |
| } |
| // JDK compatibility mode: Remove SKIP_SYNTAX. |
| return MessageImpl::appendSubMessageWithoutSkipSyntax(msgPattern, msgStart, appendTo); |
| } |
| |
| int32_t |
| ChoiceFormat::findSubMessage(const MessagePattern &pattern, int32_t partIndex, double number) { |
| int32_t count = pattern.countParts(); |
| int32_t msgStart; |
| // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples |
| // until ARG_LIMIT or end of choice-only pattern. |
| // Ignore the first number and selector and start the loop on the first message. |
| partIndex += 2; |
| for (;;) { |
| // Skip but remember the current sub-message. |
| msgStart = partIndex; |
| partIndex = pattern.getLimitPartIndex(partIndex); |
| if (++partIndex >= count) { |
| // Reached the end of the choice-only pattern. |
| // Return with the last sub-message. |
| break; |
| } |
| const MessagePattern::Part &part = pattern.getPart(partIndex++); |
| UMessagePatternPartType type = part.getType(); |
| if (type == UMSGPAT_PART_TYPE_ARG_LIMIT) { |
| // Reached the end of the ChoiceFormat style. |
| // Return with the last sub-message. |
| break; |
| } |
| // part is an ARG_INT or ARG_DOUBLE |
| U_ASSERT(MessagePattern::Part::hasNumericValue(type)); |
| double boundary = pattern.getNumericValue(part); |
| // Fetch the ARG_SELECTOR character. |
| int32_t selectorIndex = pattern.getPatternIndex(partIndex++); |
| UChar boundaryChar = pattern.getPatternString().charAt(selectorIndex); |
| if (boundaryChar == LESS_THAN ? !(number > boundary) : !(number >= boundary)) { |
| // The number is in the interval between the previous boundary and the current one. |
| // Return with the sub-message between them. |
| // The !(a>b) and !(a>=b) comparisons are equivalent to |
| // (a<=b) and (a<b) except they "catch" NaN. |
| break; |
| } |
| } |
| return msgStart; |
| } |
| |
| // ------------------------------------- |
| // 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; |
| } |
| if (msgPattern.countParts() == 0) { |
| status = U_INVALID_STATE_ERROR; |
| return appendTo; |
| } |
| |
| for (int32_t i = 0; i < cnt; i++) { |
| double objDouble = objs[i].getDouble(status); |
| if (U_SUCCESS(status)) { |
| format(objDouble, appendTo, pos); |
| } |
| } |
| |
| return appendTo; |
| } |
| |
| // ------------------------------------- |
| |
| void |
| ChoiceFormat::parse(const UnicodeString& text, |
| Formattable& result, |
| ParsePosition& pos) const |
| { |
| result.setDouble(parseArgument(msgPattern, 0, text, pos)); |
| } |
| |
| double |
| ChoiceFormat::parseArgument( |
| const MessagePattern &pattern, int32_t partIndex, |
| const UnicodeString &source, ParsePosition &pos) { |
| // find the best number (defined as the one with the longest parse) |
| int32_t start = pos.getIndex(); |
| int32_t furthest = start; |
| double bestNumber = uprv_getNaN(); |
| double tempNumber = 0.0; |
| int32_t count = pattern.countParts(); |
| while (partIndex < count && pattern.getPartType(partIndex) != UMSGPAT_PART_TYPE_ARG_LIMIT) { |
| tempNumber = pattern.getNumericValue(pattern.getPart(partIndex)); |
| partIndex += 2; // skip the numeric part and ignore the ARG_SELECTOR |
| int32_t msgLimit = pattern.getLimitPartIndex(partIndex); |
| int32_t len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start); |
| if (len >= 0) { |
| int32_t newIndex = start + len; |
| if (newIndex > furthest) { |
| furthest = newIndex; |
| bestNumber = tempNumber; |
| if (furthest == source.length()) { |
| break; |
| } |
| } |
| } |
| partIndex = msgLimit + 1; |
| } |
| if (furthest == start) { |
| pos.setErrorIndex(start); |
| } else { |
| pos.setIndex(furthest); |
| } |
| return bestNumber; |
| } |
| |
| int32_t |
| ChoiceFormat::matchStringUntilLimitPart( |
| const MessagePattern &pattern, int32_t partIndex, int32_t limitPartIndex, |
| const UnicodeString &source, int32_t sourceOffset) { |
| int32_t matchingSourceLength = 0; |
| const UnicodeString &msgString = pattern.getPatternString(); |
| int32_t prevIndex = pattern.getPart(partIndex).getLimit(); |
| for (;;) { |
| const MessagePattern::Part &part = pattern.getPart(++partIndex); |
| if (partIndex == limitPartIndex || part.getType() == UMSGPAT_PART_TYPE_SKIP_SYNTAX) { |
| int32_t index = part.getIndex(); |
| int32_t length = index - prevIndex; |
| if (length != 0 && 0 != source.compare(sourceOffset, length, msgString, prevIndex, length)) { |
| return -1; // mismatch |
| } |
| matchingSourceLength += length; |
| if (partIndex == limitPartIndex) { |
| return matchingSourceLength; |
| } |
| prevIndex = part.getLimit(); // SKIP_SYNTAX |
| } |
| } |
| } |
| |
| // ------------------------------------- |
| |
| ChoiceFormat* |
| ChoiceFormat::clone() const |
| { |
| ChoiceFormat *aCopy = new ChoiceFormat(*this); |
| return aCopy; |
| } |
| |
| U_NAMESPACE_END |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |
| |
| //eof |