1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 1997-2013, International Business Machines Corporation and *
6 * others. All Rights Reserved. *
7 *******************************************************************************
11 * Modification History:
13 * Date Name Description
14 * 02/19/97 aliu Converted from java.
15 * 03/20/97 helena Finished first cut of implementation and got rid
16 * of nextDouble/previousDouble and replaced with
18 * 4/10/97 aliu Clean up. Modified to work on AIX.
19 * 06/04/97 helena Fixed applyPattern(), toPattern() and not to include
21 * 07/09/97 helena Made ParsePosition into a class.
22 * 08/06/97 nos removed overloaded constructor, fixed 'format(array)'
23 * 07/22/98 stephen JDK 1.2 Sync - removed UBool array (doubleFlags)
24 * 02/22/99 stephen Removed character literals for EBCDIC safety
25 ********************************************************************************
28 #include "unicode/utypes.h"
30 #if !UCONFIG_NO_FORMATTING
32 #include "unicode/choicfmt.h"
33 #include "unicode/numfmt.h"
34 #include "unicode/locid.h"
37 #include "messageimpl.h"
43 // *****************************************************************************
45 // *****************************************************************************
49 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChoiceFormat
)
51 // Special characters used by ChoiceFormat. There are two characters
52 // used interchangeably to indicate <=. Either is parsed, but only
53 // LESS_EQUAL is generated by toPattern().
54 #define SINGLE_QUOTE ((UChar)0x0027) /*'*/
55 #define LESS_THAN ((UChar)0x003C) /*<*/
56 #define LESS_EQUAL ((UChar)0x0023) /*#*/
57 #define LESS_EQUAL2 ((UChar)0x2264)
58 #define VERTICAL_BAR ((UChar)0x007C) /*|*/
59 #define MINUS ((UChar)0x002D) /*-*/
61 static const UChar LEFT_CURLY_BRACE
= 0x7B; /*{*/
62 static const UChar RIGHT_CURLY_BRACE
= 0x7D; /*}*/
67 #define INFINITY ((UChar)0x221E)
69 //static const UChar gPositiveInfinity[] = {INFINITY, 0};
70 //static const UChar gNegativeInfinity[] = {MINUS, INFINITY, 0};
71 #define POSITIVE_INF_STRLEN 1
72 #define NEGATIVE_INF_STRLEN 2
74 // -------------------------------------
75 // Creates a ChoiceFormat instance based on the pattern.
77 ChoiceFormat::ChoiceFormat(const UnicodeString
& newPattern
,
79 : constructorErrorCode(status
),
82 applyPattern(newPattern
, status
);
85 // -------------------------------------
86 // Creates a ChoiceFormat instance with the limit array and
87 // format strings for each limit.
89 ChoiceFormat::ChoiceFormat(const double* limits
,
90 const UnicodeString
* formats
,
92 : constructorErrorCode(U_ZERO_ERROR
),
93 msgPattern(constructorErrorCode
)
95 setChoices(limits
, NULL
, formats
, cnt
, constructorErrorCode
);
98 // -------------------------------------
100 ChoiceFormat::ChoiceFormat(const double* limits
,
101 const UBool
* closures
,
102 const UnicodeString
* formats
,
104 : constructorErrorCode(U_ZERO_ERROR
),
105 msgPattern(constructorErrorCode
)
107 setChoices(limits
, closures
, formats
, cnt
, constructorErrorCode
);
110 // -------------------------------------
113 ChoiceFormat::ChoiceFormat(const ChoiceFormat
& that
)
114 : NumberFormat(that
),
115 constructorErrorCode(that
.constructorErrorCode
),
116 msgPattern(that
.msgPattern
)
120 // -------------------------------------
121 // Private constructor that creates a
122 // ChoiceFormat instance based on the
123 // pattern and populates UParseError
125 ChoiceFormat::ChoiceFormat(const UnicodeString
& newPattern
,
126 UParseError
& parseError
,
128 : constructorErrorCode(status
),
131 applyPattern(newPattern
,parseError
, status
);
133 // -------------------------------------
136 ChoiceFormat::operator==(const Format
& that
) const
138 if (this == &that
) return TRUE
;
139 if (!NumberFormat::operator==(that
)) return FALSE
;
140 ChoiceFormat
& thatAlias
= (ChoiceFormat
&)that
;
141 return msgPattern
== thatAlias
.msgPattern
;
144 // -------------------------------------
148 ChoiceFormat::operator=(const ChoiceFormat
& that
)
151 NumberFormat::operator=(that
);
152 constructorErrorCode
= that
.constructorErrorCode
;
153 msgPattern
= that
.msgPattern
;
158 // -------------------------------------
160 ChoiceFormat::~ChoiceFormat()
164 // -------------------------------------
167 * Convert a double value to a string without the overhead of NumberFormat.
170 ChoiceFormat::dtos(double value
,
171 UnicodeString
& string
)
173 /* Buffer to contain the digits and any extra formatting stuff. */
174 char temp
[DBL_DIG
+ 16];
178 sprintf(temp
, "%.*g", DBL_DIG
, value
);
180 /* Find and convert the decimal point.
181 Using setlocale on some machines will cause sprintf to use a comma for certain locales.
183 while (*itrPtr
&& (*itrPtr
== '-' || isdigit(*itrPtr
))) {
186 if (*itrPtr
!= 0 && *itrPtr
!= 'e') {
187 /* We reached something that looks like a decimal point.
188 In case someone used setlocale(), which changes the decimal point. */
192 /* Search for the exponent */
193 while (*itrPtr
&& *itrPtr
!= 'e') {
196 if (*itrPtr
== 'e') {
198 /* Verify the exponent sign */
199 if (*itrPtr
== '+' || *itrPtr
== '-') {
202 /* Remove leading zeros. You will see this on Windows machines. */
204 while (*itrPtr
== '0') {
207 if (*itrPtr
&& expPtr
!= itrPtr
) {
208 /* Shift the exponent without zeros. */
210 *(expPtr
++) = *(itrPtr
++);
217 string
= UnicodeString(temp
, -1, US_INV
); /* invariant codepage */
221 // -------------------------------------
222 // calls the overloaded applyPattern method.
225 ChoiceFormat::applyPattern(const UnicodeString
& pattern
,
228 msgPattern
.parseChoiceStyle(pattern
, NULL
, status
);
229 constructorErrorCode
= status
;
232 // -------------------------------------
233 // Applies the pattern to this ChoiceFormat instance.
236 ChoiceFormat::applyPattern(const UnicodeString
& pattern
,
237 UParseError
& parseError
,
240 msgPattern
.parseChoiceStyle(pattern
, &parseError
, status
);
241 constructorErrorCode
= status
;
243 // -------------------------------------
244 // Returns the input pattern string.
247 ChoiceFormat::toPattern(UnicodeString
& result
) const
249 return result
= msgPattern
.getPatternString();
252 // -------------------------------------
253 // Sets the limit and format arrays.
255 ChoiceFormat::setChoices( const double* limits
,
256 const UnicodeString
* formats
,
259 UErrorCode errorCode
= U_ZERO_ERROR
;
260 setChoices(limits
, NULL
, formats
, cnt
, errorCode
);
263 // -------------------------------------
264 // Sets the limit and format arrays.
266 ChoiceFormat::setChoices( const double* limits
,
267 const UBool
* closures
,
268 const UnicodeString
* formats
,
271 UErrorCode errorCode
= U_ZERO_ERROR
;
272 setChoices(limits
, closures
, formats
, cnt
, errorCode
);
276 ChoiceFormat::setChoices(const double* limits
,
277 const UBool
* closures
,
278 const UnicodeString
* formats
,
280 UErrorCode
&errorCode
) {
281 if (U_FAILURE(errorCode
)) {
284 if (limits
== NULL
|| formats
== NULL
) {
285 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
288 // Reconstruct the original input pattern.
289 // Modified version of the pre-ICU 4.8 toPattern() implementation.
290 UnicodeString result
;
291 for (int32_t i
= 0; i
< count
; ++i
) {
293 result
+= VERTICAL_BAR
;
296 if (uprv_isPositiveInfinity(limits
[i
])) {
298 } else if (uprv_isNegativeInfinity(limits
[i
])) {
302 result
+= dtos(limits
[i
], buf
);
304 if (closures
!= NULL
&& closures
[i
]) {
307 result
+= LESS_EQUAL
;
309 // Append formats[i], using quotes if there are special
310 // characters. Single quotes themselves must be escaped in
312 const UnicodeString
& text
= formats
[i
];
313 int32_t textLength
= text
.length();
314 int32_t nestingLevel
= 0;
315 for (int32_t j
= 0; j
< textLength
; ++j
) {
317 if (c
== SINGLE_QUOTE
&& nestingLevel
== 0) {
318 // Double each top-level apostrophe.
320 } else if (c
== VERTICAL_BAR
&& nestingLevel
== 0) {
321 // Surround each pipe symbol with apostrophes for quoting.
322 // If the next character is an apostrophe, then that will be doubled,
323 // and although the parser will see the apostrophe pairs beginning
324 // and ending one character earlier than our doubling, the result
328 // |'' -> '|''''' etc.
329 result
.append(SINGLE_QUOTE
).append(c
).append(SINGLE_QUOTE
);
330 continue; // Skip the append(c) at the end of the loop body.
331 } else if (c
== LEFT_CURLY_BRACE
) {
333 } else if (c
== RIGHT_CURLY_BRACE
&& nestingLevel
> 0) {
339 // Apply the reconstructed pattern.
340 applyPattern(result
, errorCode
);
343 // -------------------------------------
344 // Gets the limit array.
347 ChoiceFormat::getLimits(int32_t& cnt
) const
353 // -------------------------------------
354 // Gets the closures array.
357 ChoiceFormat::getClosures(int32_t& cnt
) const
363 // -------------------------------------
364 // Gets the format array.
367 ChoiceFormat::getFormats(int32_t& cnt
) const
373 // -------------------------------------
374 // Formats an int64 number, it's actually formatted as
375 // a double. The returned format string may differ
376 // from the input number because of this.
379 ChoiceFormat::format(int64_t number
,
380 UnicodeString
& appendTo
,
381 FieldPosition
& status
) const
383 return format((double) number
, appendTo
, status
);
386 // -------------------------------------
387 // Formats an int32_t number, it's actually formatted as
391 ChoiceFormat::format(int32_t number
,
392 UnicodeString
& appendTo
,
393 FieldPosition
& status
) const
395 return format((double) number
, appendTo
, status
);
398 // -------------------------------------
399 // Formats a double number.
402 ChoiceFormat::format(double number
,
403 UnicodeString
& appendTo
,
404 FieldPosition
& /*pos*/) const
406 if (msgPattern
.countParts() == 0) {
407 // No pattern was applied, or it failed.
410 // Get the appropriate sub-message.
411 int32_t msgStart
= findSubMessage(msgPattern
, 0, number
);
412 if (!MessageImpl::jdkAposMode(msgPattern
)) {
413 int32_t patternStart
= msgPattern
.getPart(msgStart
).getLimit();
414 int32_t msgLimit
= msgPattern
.getLimitPartIndex(msgStart
);
415 appendTo
.append(msgPattern
.getPatternString(),
417 msgPattern
.getPatternIndex(msgLimit
) - patternStart
);
420 // JDK compatibility mode: Remove SKIP_SYNTAX.
421 return MessageImpl::appendSubMessageWithoutSkipSyntax(msgPattern
, msgStart
, appendTo
);
425 ChoiceFormat::findSubMessage(const MessagePattern
&pattern
, int32_t partIndex
, double number
) {
426 int32_t count
= pattern
.countParts();
428 // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
429 // until ARG_LIMIT or end of choice-only pattern.
430 // Ignore the first number and selector and start the loop on the first message.
433 // Skip but remember the current sub-message.
434 msgStart
= partIndex
;
435 partIndex
= pattern
.getLimitPartIndex(partIndex
);
436 if (++partIndex
>= count
) {
437 // Reached the end of the choice-only pattern.
438 // Return with the last sub-message.
441 const MessagePattern::Part
&part
= pattern
.getPart(partIndex
++);
442 UMessagePatternPartType type
= part
.getType();
443 if (type
== UMSGPAT_PART_TYPE_ARG_LIMIT
) {
444 // Reached the end of the ChoiceFormat style.
445 // Return with the last sub-message.
448 // part is an ARG_INT or ARG_DOUBLE
449 U_ASSERT(MessagePattern::Part::hasNumericValue(type
));
450 double boundary
= pattern
.getNumericValue(part
);
451 // Fetch the ARG_SELECTOR character.
452 int32_t selectorIndex
= pattern
.getPatternIndex(partIndex
++);
453 UChar boundaryChar
= pattern
.getPatternString().charAt(selectorIndex
);
454 if (boundaryChar
== LESS_THAN
? !(number
> boundary
) : !(number
>= boundary
)) {
455 // The number is in the interval between the previous boundary and the current one.
456 // Return with the sub-message between them.
457 // The !(a>b) and !(a>=b) comparisons are equivalent to
458 // (a<=b) and (a<b) except they "catch" NaN.
465 // -------------------------------------
466 // Formats an array of objects. Checks if the data type of the objects
467 // to get the right value for formatting.
470 ChoiceFormat::format(const Formattable
* objs
,
472 UnicodeString
& appendTo
,
474 UErrorCode
& status
) const
477 status
= U_ILLEGAL_ARGUMENT_ERROR
;
480 if (msgPattern
.countParts() == 0) {
481 status
= U_INVALID_STATE_ERROR
;
485 for (int32_t i
= 0; i
< cnt
; i
++) {
486 double objDouble
= objs
[i
].getDouble(status
);
487 if (U_SUCCESS(status
)) {
488 format(objDouble
, appendTo
, pos
);
495 // -------------------------------------
498 ChoiceFormat::parse(const UnicodeString
& text
,
500 ParsePosition
& pos
) const
502 result
.setDouble(parseArgument(msgPattern
, 0, text
, pos
));
506 ChoiceFormat::parseArgument(
507 const MessagePattern
&pattern
, int32_t partIndex
,
508 const UnicodeString
&source
, ParsePosition
&pos
) {
509 // find the best number (defined as the one with the longest parse)
510 int32_t start
= pos
.getIndex();
511 int32_t furthest
= start
;
512 double bestNumber
= uprv_getNaN();
513 double tempNumber
= 0.0;
514 int32_t count
= pattern
.countParts();
515 while (partIndex
< count
&& pattern
.getPartType(partIndex
) != UMSGPAT_PART_TYPE_ARG_LIMIT
) {
516 tempNumber
= pattern
.getNumericValue(pattern
.getPart(partIndex
));
517 partIndex
+= 2; // skip the numeric part and ignore the ARG_SELECTOR
518 int32_t msgLimit
= pattern
.getLimitPartIndex(partIndex
);
519 int32_t len
= matchStringUntilLimitPart(pattern
, partIndex
, msgLimit
, source
, start
);
521 int32_t newIndex
= start
+ len
;
522 if (newIndex
> furthest
) {
524 bestNumber
= tempNumber
;
525 if (furthest
== source
.length()) {
530 partIndex
= msgLimit
+ 1;
532 if (furthest
== start
) {
533 pos
.setErrorIndex(start
);
535 pos
.setIndex(furthest
);
541 ChoiceFormat::matchStringUntilLimitPart(
542 const MessagePattern
&pattern
, int32_t partIndex
, int32_t limitPartIndex
,
543 const UnicodeString
&source
, int32_t sourceOffset
) {
544 int32_t matchingSourceLength
= 0;
545 const UnicodeString
&msgString
= pattern
.getPatternString();
546 int32_t prevIndex
= pattern
.getPart(partIndex
).getLimit();
548 const MessagePattern::Part
&part
= pattern
.getPart(++partIndex
);
549 if (partIndex
== limitPartIndex
|| part
.getType() == UMSGPAT_PART_TYPE_SKIP_SYNTAX
) {
550 int32_t index
= part
.getIndex();
551 int32_t length
= index
- prevIndex
;
552 if (length
!= 0 && 0 != source
.compare(sourceOffset
, length
, msgString
, prevIndex
, length
)) {
553 return -1; // mismatch
555 matchingSourceLength
+= length
;
556 if (partIndex
== limitPartIndex
) {
557 return matchingSourceLength
;
559 prevIndex
= part
.getLimit(); // SKIP_SYNTAX
564 // -------------------------------------
567 ChoiceFormat::clone() const
569 ChoiceFormat
*aCopy
= new ChoiceFormat(*this);
575 #endif /* #if !UCONFIG_NO_FORMATTING */