2 *******************************************************************************
3 * Copyright (C) 1997-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
9 * Modification History:
11 * Date Name Description
12 * 02/19/97 aliu Converted from java.
13 * 03/20/97 helena Finished first cut of implementation and got rid
14 * of nextDouble/previousDouble and replaced with
16 * 4/10/97 aliu Clean up. Modified to work on AIX.
17 * 06/04/97 helena Fixed applyPattern(), toPattern() and not to include
19 * 07/09/97 helena Made ParsePosition into a class.
20 * 08/06/97 nos removed overloaded constructor, fixed 'format(array)'
21 * 07/22/98 stephen JDK 1.2 Sync - removed UBool array (doubleFlags)
22 * 02/22/99 stephen Removed character literals for EBCDIC safety
23 ********************************************************************************
26 #include "unicode/utypes.h"
28 #if !UCONFIG_NO_FORMATTING
30 #include "unicode/choicfmt.h"
31 #include "unicode/numfmt.h"
32 #include "unicode/locid.h"
35 #include "messageimpl.h"
41 // *****************************************************************************
43 // *****************************************************************************
47 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChoiceFormat
)
49 // Special characters used by ChoiceFormat. There are two characters
50 // used interchangeably to indicate <=. Either is parsed, but only
51 // LESS_EQUAL is generated by toPattern().
52 #define SINGLE_QUOTE ((UChar)0x0027) /*'*/
53 #define LESS_THAN ((UChar)0x003C) /*<*/
54 #define LESS_EQUAL ((UChar)0x0023) /*#*/
55 #define LESS_EQUAL2 ((UChar)0x2264)
56 #define VERTICAL_BAR ((UChar)0x007C) /*|*/
57 #define MINUS ((UChar)0x002D) /*-*/
59 static const UChar LEFT_CURLY_BRACE
= 0x7B; /*{*/
60 static const UChar RIGHT_CURLY_BRACE
= 0x7D; /*}*/
65 #define INFINITY ((UChar)0x221E)
67 //static const UChar gPositiveInfinity[] = {INFINITY, 0};
68 //static const UChar gNegativeInfinity[] = {MINUS, INFINITY, 0};
69 #define POSITIVE_INF_STRLEN 1
70 #define NEGATIVE_INF_STRLEN 2
72 // -------------------------------------
73 // Creates a ChoiceFormat instance based on the pattern.
75 ChoiceFormat::ChoiceFormat(const UnicodeString
& newPattern
,
77 : constructorErrorCode(status
),
80 applyPattern(newPattern
, status
);
83 // -------------------------------------
84 // Creates a ChoiceFormat instance with the limit array and
85 // format strings for each limit.
87 ChoiceFormat::ChoiceFormat(const double* limits
,
88 const UnicodeString
* formats
,
90 : constructorErrorCode(U_ZERO_ERROR
),
91 msgPattern(constructorErrorCode
)
93 setChoices(limits
, NULL
, formats
, cnt
, constructorErrorCode
);
96 // -------------------------------------
98 ChoiceFormat::ChoiceFormat(const double* limits
,
99 const UBool
* closures
,
100 const UnicodeString
* formats
,
102 : constructorErrorCode(U_ZERO_ERROR
),
103 msgPattern(constructorErrorCode
)
105 setChoices(limits
, closures
, formats
, cnt
, constructorErrorCode
);
108 // -------------------------------------
111 ChoiceFormat::ChoiceFormat(const ChoiceFormat
& that
)
112 : NumberFormat(that
),
113 constructorErrorCode(that
.constructorErrorCode
),
114 msgPattern(that
.msgPattern
)
118 // -------------------------------------
119 // Private constructor that creates a
120 // ChoiceFormat instance based on the
121 // pattern and populates UParseError
123 ChoiceFormat::ChoiceFormat(const UnicodeString
& newPattern
,
124 UParseError
& parseError
,
126 : constructorErrorCode(status
),
129 applyPattern(newPattern
,parseError
, status
);
131 // -------------------------------------
134 ChoiceFormat::operator==(const Format
& that
) const
136 if (this == &that
) return TRUE
;
137 if (!NumberFormat::operator==(that
)) return FALSE
;
138 ChoiceFormat
& thatAlias
= (ChoiceFormat
&)that
;
139 return msgPattern
== thatAlias
.msgPattern
;
142 // -------------------------------------
146 ChoiceFormat::operator=(const ChoiceFormat
& that
)
149 NumberFormat::operator=(that
);
150 constructorErrorCode
= that
.constructorErrorCode
;
151 msgPattern
= that
.msgPattern
;
156 // -------------------------------------
158 ChoiceFormat::~ChoiceFormat()
162 // -------------------------------------
165 * Convert a double value to a string without the overhead of NumberFormat.
168 ChoiceFormat::dtos(double value
,
169 UnicodeString
& string
)
171 /* Buffer to contain the digits and any extra formatting stuff. */
172 char temp
[DBL_DIG
+ 16];
176 sprintf(temp
, "%.*g", DBL_DIG
, value
);
178 /* Find and convert the decimal point.
179 Using setlocale on some machines will cause sprintf to use a comma for certain locales.
181 while (*itrPtr
&& (*itrPtr
== '-' || isdigit(*itrPtr
))) {
184 if (*itrPtr
!= 0 && *itrPtr
!= 'e') {
185 /* We reached something that looks like a decimal point.
186 In case someone used setlocale(), which changes the decimal point. */
190 /* Search for the exponent */
191 while (*itrPtr
&& *itrPtr
!= 'e') {
194 if (*itrPtr
== 'e') {
196 /* Verify the exponent sign */
197 if (*itrPtr
== '+' || *itrPtr
== '-') {
200 /* Remove leading zeros. You will see this on Windows machines. */
202 while (*itrPtr
== '0') {
205 if (*itrPtr
&& expPtr
!= itrPtr
) {
206 /* Shift the exponent without zeros. */
208 *(expPtr
++) = *(itrPtr
++);
215 string
= UnicodeString(temp
, -1, US_INV
); /* invariant codepage */
219 // -------------------------------------
220 // calls the overloaded applyPattern method.
223 ChoiceFormat::applyPattern(const UnicodeString
& pattern
,
226 msgPattern
.parseChoiceStyle(pattern
, NULL
, status
);
227 constructorErrorCode
= status
;
230 // -------------------------------------
231 // Applies the pattern to this ChoiceFormat instance.
234 ChoiceFormat::applyPattern(const UnicodeString
& pattern
,
235 UParseError
& parseError
,
238 msgPattern
.parseChoiceStyle(pattern
, &parseError
, status
);
239 constructorErrorCode
= status
;
241 // -------------------------------------
242 // Returns the input pattern string.
245 ChoiceFormat::toPattern(UnicodeString
& result
) const
247 return result
= msgPattern
.getPatternString();
250 // -------------------------------------
251 // Sets the limit and format arrays.
253 ChoiceFormat::setChoices( const double* limits
,
254 const UnicodeString
* formats
,
257 UErrorCode errorCode
= U_ZERO_ERROR
;
258 setChoices(limits
, NULL
, formats
, cnt
, errorCode
);
261 // -------------------------------------
262 // Sets the limit and format arrays.
264 ChoiceFormat::setChoices( const double* limits
,
265 const UBool
* closures
,
266 const UnicodeString
* formats
,
269 UErrorCode errorCode
= U_ZERO_ERROR
;
270 setChoices(limits
, closures
, formats
, cnt
, errorCode
);
274 ChoiceFormat::setChoices(const double* limits
,
275 const UBool
* closures
,
276 const UnicodeString
* formats
,
278 UErrorCode
&errorCode
) {
279 if (U_FAILURE(errorCode
)) {
282 if (limits
== NULL
|| formats
== NULL
) {
283 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
286 // Reconstruct the original input pattern.
287 // Modified version of the pre-ICU 4.8 toPattern() implementation.
288 UnicodeString result
;
289 for (int32_t i
= 0; i
< count
; ++i
) {
291 result
+= VERTICAL_BAR
;
294 if (uprv_isPositiveInfinity(limits
[i
])) {
296 } else if (uprv_isNegativeInfinity(limits
[i
])) {
300 result
+= dtos(limits
[i
], buf
);
302 if (closures
!= NULL
&& closures
[i
]) {
305 result
+= LESS_EQUAL
;
307 // Append formats[i], using quotes if there are special
308 // characters. Single quotes themselves must be escaped in
310 const UnicodeString
& text
= formats
[i
];
311 int32_t textLength
= text
.length();
312 int32_t nestingLevel
= 0;
313 for (int32_t j
= 0; j
< textLength
; ++j
) {
315 if (c
== SINGLE_QUOTE
&& nestingLevel
== 0) {
316 // Double each top-level apostrophe.
318 } else if (c
== VERTICAL_BAR
&& nestingLevel
== 0) {
319 // Surround each pipe symbol with apostrophes for quoting.
320 // If the next character is an apostrophe, then that will be doubled,
321 // and although the parser will see the apostrophe pairs beginning
322 // and ending one character earlier than our doubling, the result
326 // |'' -> '|''''' etc.
327 result
.append(SINGLE_QUOTE
).append(c
).append(SINGLE_QUOTE
);
328 continue; // Skip the append(c) at the end of the loop body.
329 } else if (c
== LEFT_CURLY_BRACE
) {
331 } else if (c
== RIGHT_CURLY_BRACE
&& nestingLevel
> 0) {
337 // Apply the reconstructed pattern.
338 applyPattern(result
, errorCode
);
341 // -------------------------------------
342 // Gets the limit array.
345 ChoiceFormat::getLimits(int32_t& cnt
) const
351 // -------------------------------------
352 // Gets the closures array.
355 ChoiceFormat::getClosures(int32_t& cnt
) const
361 // -------------------------------------
362 // Gets the format array.
365 ChoiceFormat::getFormats(int32_t& cnt
) const
371 // -------------------------------------
372 // Formats an int64 number, it's actually formatted as
373 // a double. The returned format string may differ
374 // from the input number because of this.
377 ChoiceFormat::format(int64_t number
,
378 UnicodeString
& appendTo
,
379 FieldPosition
& status
) const
381 return format((double) number
, appendTo
, status
);
384 // -------------------------------------
385 // Formats an int32_t number, it's actually formatted as
389 ChoiceFormat::format(int32_t number
,
390 UnicodeString
& appendTo
,
391 FieldPosition
& status
) const
393 return format((double) number
, appendTo
, status
);
396 // -------------------------------------
397 // Formats a double number.
400 ChoiceFormat::format(double number
,
401 UnicodeString
& appendTo
,
402 FieldPosition
& /*pos*/) const
404 if (msgPattern
.countParts() == 0) {
405 // No pattern was applied, or it failed.
408 // Get the appropriate sub-message.
409 int32_t msgStart
= findSubMessage(msgPattern
, 0, number
);
410 if (!MessageImpl::jdkAposMode(msgPattern
)) {
411 int32_t patternStart
= msgPattern
.getPart(msgStart
).getLimit();
412 int32_t msgLimit
= msgPattern
.getLimitPartIndex(msgStart
);
413 appendTo
.append(msgPattern
.getPatternString(),
415 msgPattern
.getPatternIndex(msgLimit
) - patternStart
);
418 // JDK compatibility mode: Remove SKIP_SYNTAX.
419 return MessageImpl::appendSubMessageWithoutSkipSyntax(msgPattern
, msgStart
, appendTo
);
423 ChoiceFormat::findSubMessage(const MessagePattern
&pattern
, int32_t partIndex
, double number
) {
424 int32_t count
= pattern
.countParts();
426 // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
427 // until ARG_LIMIT or end of choice-only pattern.
428 // Ignore the first number and selector and start the loop on the first message.
431 // Skip but remember the current sub-message.
432 msgStart
= partIndex
;
433 partIndex
= pattern
.getLimitPartIndex(partIndex
);
434 if (++partIndex
>= count
) {
435 // Reached the end of the choice-only pattern.
436 // Return with the last sub-message.
439 const MessagePattern::Part
&part
= pattern
.getPart(partIndex
++);
440 UMessagePatternPartType type
= part
.getType();
441 if (type
== UMSGPAT_PART_TYPE_ARG_LIMIT
) {
442 // Reached the end of the ChoiceFormat style.
443 // Return with the last sub-message.
446 // part is an ARG_INT or ARG_DOUBLE
447 U_ASSERT(MessagePattern::Part::hasNumericValue(type
));
448 double boundary
= pattern
.getNumericValue(part
);
449 // Fetch the ARG_SELECTOR character.
450 int32_t selectorIndex
= pattern
.getPatternIndex(partIndex
++);
451 UChar boundaryChar
= pattern
.getPatternString().charAt(selectorIndex
);
452 if (boundaryChar
== LESS_THAN
? !(number
> boundary
) : !(number
>= boundary
)) {
453 // The number is in the interval between the previous boundary and the current one.
454 // Return with the sub-message between them.
455 // The !(a>b) and !(a>=b) comparisons are equivalent to
456 // (a<=b) and (a<b) except they "catch" NaN.
463 // -------------------------------------
464 // Formats an array of objects. Checks if the data type of the objects
465 // to get the right value for formatting.
468 ChoiceFormat::format(const Formattable
* objs
,
470 UnicodeString
& appendTo
,
472 UErrorCode
& status
) const
475 status
= U_ILLEGAL_ARGUMENT_ERROR
;
478 if (msgPattern
.countParts() == 0) {
479 status
= U_INVALID_STATE_ERROR
;
483 for (int32_t i
= 0; i
< cnt
; i
++) {
484 double objDouble
= objs
[i
].getDouble(status
);
485 if (U_SUCCESS(status
)) {
486 format(objDouble
, appendTo
, pos
);
493 // -------------------------------------
496 ChoiceFormat::parse(const UnicodeString
& text
,
498 ParsePosition
& pos
) const
500 result
.setDouble(parseArgument(msgPattern
, 0, text
, pos
));
504 ChoiceFormat::parseArgument(
505 const MessagePattern
&pattern
, int32_t partIndex
,
506 const UnicodeString
&source
, ParsePosition
&pos
) {
507 // find the best number (defined as the one with the longest parse)
508 int32_t start
= pos
.getIndex();
509 int32_t furthest
= start
;
510 double bestNumber
= uprv_getNaN();
511 double tempNumber
= 0.0;
512 int32_t count
= pattern
.countParts();
513 while (partIndex
< count
&& pattern
.getPartType(partIndex
) != UMSGPAT_PART_TYPE_ARG_LIMIT
) {
514 tempNumber
= pattern
.getNumericValue(pattern
.getPart(partIndex
));
515 partIndex
+= 2; // skip the numeric part and ignore the ARG_SELECTOR
516 int32_t msgLimit
= pattern
.getLimitPartIndex(partIndex
);
517 int32_t len
= matchStringUntilLimitPart(pattern
, partIndex
, msgLimit
, source
, start
);
519 int32_t newIndex
= start
+ len
;
520 if (newIndex
> furthest
) {
522 bestNumber
= tempNumber
;
523 if (furthest
== source
.length()) {
528 partIndex
= msgLimit
+ 1;
530 if (furthest
== start
) {
531 pos
.setErrorIndex(start
);
533 pos
.setIndex(furthest
);
539 ChoiceFormat::matchStringUntilLimitPart(
540 const MessagePattern
&pattern
, int32_t partIndex
, int32_t limitPartIndex
,
541 const UnicodeString
&source
, int32_t sourceOffset
) {
542 int32_t matchingSourceLength
= 0;
543 const UnicodeString
&msgString
= pattern
.getPatternString();
544 int32_t prevIndex
= pattern
.getPart(partIndex
).getLimit();
546 const MessagePattern::Part
&part
= pattern
.getPart(++partIndex
);
547 if (partIndex
== limitPartIndex
|| part
.getType() == UMSGPAT_PART_TYPE_SKIP_SYNTAX
) {
548 int32_t index
= part
.getIndex();
549 int32_t length
= index
- prevIndex
;
550 if (length
!= 0 && 0 != source
.compare(sourceOffset
, length
, msgString
, prevIndex
, length
)) {
551 return -1; // mismatch
553 matchingSourceLength
+= length
;
554 if (partIndex
== limitPartIndex
) {
555 return matchingSourceLength
;
557 prevIndex
= part
.getLimit(); // SKIP_SYNTAX
562 // -------------------------------------
565 ChoiceFormat::clone() const
567 ChoiceFormat
*aCopy
= new ChoiceFormat(*this);
573 #endif /* #if !UCONFIG_NO_FORMATTING */