]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/choicfmt.cpp
ICU-491.11.1.tar.gz
[apple/icu.git] / icuSources / i18n / choicfmt.cpp
CommitLineData
b75a7d8f
A
1/*
2*******************************************************************************
4388f060 3* Copyright (C) 1997-2011, International Business Machines Corporation and *
b75a7d8f
A
4* others. All Rights Reserved. *
5*******************************************************************************
6*
7* File CHOICFMT.CPP
8*
9* Modification History:
10*
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
15* boolean array.
16* 4/10/97 aliu Clean up. Modified to work on AIX.
17* 06/04/97 helena Fixed applyPattern(), toPattern() and not to include
18* wchar.h.
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********************************************************************************
24*/
25
26#include "unicode/utypes.h"
27
28#if !UCONFIG_NO_FORMATTING
29
30#include "unicode/choicfmt.h"
31#include "unicode/numfmt.h"
32#include "unicode/locid.h"
33#include "cpputils.h"
374ca955 34#include "cstring.h"
4388f060 35#include "messageimpl.h"
374ca955 36#include "putilimp.h"
4388f060 37#include "uassert.h"
73c04bcf
A
38#include <stdio.h>
39#include <float.h>
b75a7d8f
A
40
41// *****************************************************************************
42// class ChoiceFormat
43// *****************************************************************************
44
45U_NAMESPACE_BEGIN
46
374ca955 47UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChoiceFormat)
b75a7d8f
A
48
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) /*-*/
729e4ab9 58
4388f060
A
59static const UChar LEFT_CURLY_BRACE = 0x7B; /*{*/
60static const UChar RIGHT_CURLY_BRACE = 0x7D; /*}*/
61
729e4ab9
A
62#ifdef INFINITY
63#undef INFINITY
64#endif
b75a7d8f
A
65#define INFINITY ((UChar)0x221E)
66
374ca955
A
67static const UChar gPositiveInfinity[] = {INFINITY, 0};
68static const UChar gNegativeInfinity[] = {MINUS, INFINITY, 0};
b75a7d8f
A
69#define POSITIVE_INF_STRLEN 1
70#define NEGATIVE_INF_STRLEN 2
71
72// -------------------------------------
73// Creates a ChoiceFormat instance based on the pattern.
74
75ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern,
76 UErrorCode& status)
4388f060
A
77: constructorErrorCode(status),
78 msgPattern(status)
b75a7d8f
A
79{
80 applyPattern(newPattern, status);
81}
82
83// -------------------------------------
84// Creates a ChoiceFormat instance with the limit array and
85// format strings for each limit.
86
87ChoiceFormat::ChoiceFormat(const double* limits,
88 const UnicodeString* formats,
89 int32_t cnt )
4388f060
A
90: constructorErrorCode(U_ZERO_ERROR),
91 msgPattern(constructorErrorCode)
b75a7d8f 92{
4388f060 93 setChoices(limits, NULL, formats, cnt, constructorErrorCode);
b75a7d8f
A
94}
95
96// -------------------------------------
97
98ChoiceFormat::ChoiceFormat(const double* limits,
99 const UBool* closures,
100 const UnicodeString* formats,
101 int32_t cnt )
4388f060
A
102: constructorErrorCode(U_ZERO_ERROR),
103 msgPattern(constructorErrorCode)
b75a7d8f 104{
4388f060 105 setChoices(limits, closures, formats, cnt, constructorErrorCode);
b75a7d8f
A
106}
107
108// -------------------------------------
109// copy constructor
110
111ChoiceFormat::ChoiceFormat(const ChoiceFormat& that)
112: NumberFormat(that),
4388f060
A
113 constructorErrorCode(that.constructorErrorCode),
114 msgPattern(that.msgPattern)
b75a7d8f 115{
b75a7d8f
A
116}
117
118// -------------------------------------
119// Private constructor that creates a
120// ChoiceFormat instance based on the
121// pattern and populates UParseError
122
123ChoiceFormat::ChoiceFormat(const UnicodeString& newPattern,
124 UParseError& parseError,
125 UErrorCode& status)
4388f060
A
126: constructorErrorCode(status),
127 msgPattern(status)
b75a7d8f
A
128{
129 applyPattern(newPattern,parseError, status);
130}
131// -------------------------------------
132
133UBool
134ChoiceFormat::operator==(const Format& that) const
135{
136 if (this == &that) return TRUE;
b75a7d8f
A
137 if (!NumberFormat::operator==(that)) return FALSE;
138 ChoiceFormat& thatAlias = (ChoiceFormat&)that;
4388f060 139 return msgPattern == thatAlias.msgPattern;
b75a7d8f
A
140}
141
142// -------------------------------------
143// copy constructor
144
145const ChoiceFormat&
146ChoiceFormat::operator=(const ChoiceFormat& that)
147{
148 if (this != &that) {
149 NumberFormat::operator=(that);
4388f060
A
150 constructorErrorCode = that.constructorErrorCode;
151 msgPattern = that.msgPattern;
b75a7d8f
A
152 }
153 return *this;
154}
155
156// -------------------------------------
157
158ChoiceFormat::~ChoiceFormat()
159{
b75a7d8f
A
160}
161
162// -------------------------------------
163
164/**
4388f060 165 * Convert a double value to a string without the overhead of NumberFormat.
b75a7d8f
A
166 */
167UnicodeString&
168ChoiceFormat::dtos(double value,
169 UnicodeString& string)
170{
73c04bcf
A
171 /* Buffer to contain the digits and any extra formatting stuff. */
172 char temp[DBL_DIG + 16];
173 char *itrPtr = temp;
46f4442e 174 char *expPtr;
73c04bcf 175
46f4442e 176 sprintf(temp, "%.*g", DBL_DIG, value);
73c04bcf
A
177
178 /* Find and convert the decimal point.
179 Using setlocale on some machines will cause sprintf to use a comma for certain locales.
180 */
181 while (*itrPtr && (*itrPtr == '-' || isdigit(*itrPtr))) {
182 itrPtr++;
183 }
46f4442e
A
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. */
73c04bcf 187 *itrPtr = '.';
46f4442e 188 itrPtr++;
73c04bcf 189 }
46f4442e
A
190 /* Search for the exponent */
191 while (*itrPtr && *itrPtr != 'e') {
192 itrPtr++;
193 }
194 if (*itrPtr == 'e') {
195 itrPtr++;
196 /* Verify the exponent sign */
197 if (*itrPtr == '+' || *itrPtr == '-') {
198 itrPtr++;
199 }
200 /* Remove leading zeros. You will see this on Windows machines. */
201 expPtr = itrPtr;
202 while (*itrPtr == '0') {
203 itrPtr++;
204 }
205 if (*itrPtr && expPtr != itrPtr) {
206 /* Shift the exponent without zeros. */
207 while (*itrPtr) {
208 *(expPtr++) = *(itrPtr++);
209 }
210 // NULL terminate
211 *expPtr = 0;
73c04bcf
A
212 }
213 }
46f4442e 214
374ca955 215 string = UnicodeString(temp, -1, US_INV); /* invariant codepage */
b75a7d8f
A
216 return string;
217}
218
219// -------------------------------------
220// calls the overloaded applyPattern method.
221
222void
223ChoiceFormat::applyPattern(const UnicodeString& pattern,
224 UErrorCode& status)
225{
4388f060
A
226 msgPattern.parseChoiceStyle(pattern, NULL, status);
227 constructorErrorCode = status;
b75a7d8f
A
228}
229
230// -------------------------------------
231// Applies the pattern to this ChoiceFormat instance.
232
233void
234ChoiceFormat::applyPattern(const UnicodeString& pattern,
235 UParseError& parseError,
236 UErrorCode& status)
237{
4388f060
A
238 msgPattern.parseChoiceStyle(pattern, &parseError, status);
239 constructorErrorCode = status;
b75a7d8f
A
240}
241// -------------------------------------
4388f060 242// Returns the input pattern string.
b75a7d8f
A
243
244UnicodeString&
245ChoiceFormat::toPattern(UnicodeString& result) const
246{
4388f060 247 return result = msgPattern.getPatternString();
b75a7d8f
A
248}
249
b75a7d8f
A
250// -------------------------------------
251// Sets the limit and format arrays.
252void
253ChoiceFormat::setChoices( const double* limits,
254 const UnicodeString* formats,
255 int32_t cnt )
256{
4388f060
A
257 UErrorCode errorCode = U_ZERO_ERROR;
258 setChoices(limits, NULL, formats, cnt, errorCode);
b75a7d8f
A
259}
260
261// -------------------------------------
262// Sets the limit and format arrays.
263void
264ChoiceFormat::setChoices( const double* limits,
265 const UBool* closures,
266 const UnicodeString* formats,
267 int32_t cnt )
268{
4388f060
A
269 UErrorCode errorCode = U_ZERO_ERROR;
270 setChoices(limits, closures, formats, cnt, errorCode);
271}
b75a7d8f 272
4388f060
A
273void
274ChoiceFormat::setChoices(const double* limits,
275 const UBool* closures,
276 const UnicodeString* formats,
277 int32_t count,
278 UErrorCode &errorCode) {
279 if (U_FAILURE(errorCode)) {
280 return;
46f4442e 281 }
4388f060
A
282 if (limits == NULL || formats == NULL) {
283 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
284 return;
46f4442e 285 }
4388f060
A
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) {
290 if (i != 0) {
291 result += VERTICAL_BAR;
46f4442e 292 }
4388f060
A
293 UnicodeString buf;
294 if (uprv_isPositiveInfinity(limits[i])) {
295 result += INFINITY;
296 } else if (uprv_isNegativeInfinity(limits[i])) {
297 result += MINUS;
298 result += INFINITY;
299 } else {
300 result += dtos(limits[i], buf);
46f4442e 301 }
4388f060
A
302 if (closures != NULL && closures[i]) {
303 result += LESS_THAN;
304 } else {
305 result += LESS_EQUAL;
46f4442e 306 }
4388f060
A
307 // Append formats[i], using quotes if there are special
308 // characters. Single quotes themselves must be escaped in
309 // either case.
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) {
314 UChar c = text[j];
315 if (c == SINGLE_QUOTE && nestingLevel == 0) {
316 // Double each top-level apostrophe.
317 result.append(c);
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
323 // is as desired.
324 // | -> '|'
325 // |' -> '|'''
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) {
330 ++nestingLevel;
331 } else if (c == RIGHT_CURLY_BRACE && nestingLevel > 0) {
332 --nestingLevel;
333 }
334 result.append(c);
b75a7d8f
A
335 }
336 }
4388f060
A
337 // Apply the reconstructed pattern.
338 applyPattern(result, errorCode);
b75a7d8f
A
339}
340
341// -------------------------------------
342// Gets the limit array.
343
344const double*
345ChoiceFormat::getLimits(int32_t& cnt) const
346{
4388f060
A
347 cnt = 0;
348 return NULL;
b75a7d8f
A
349}
350
351// -------------------------------------
352// Gets the closures array.
353
354const UBool*
355ChoiceFormat::getClosures(int32_t& cnt) const
356{
4388f060
A
357 cnt = 0;
358 return NULL;
b75a7d8f
A
359}
360
361// -------------------------------------
362// Gets the format array.
363
364const UnicodeString*
365ChoiceFormat::getFormats(int32_t& cnt) const
366{
4388f060
A
367 cnt = 0;
368 return NULL;
b75a7d8f
A
369}
370
374ca955
A
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.
375
376UnicodeString&
377ChoiceFormat::format(int64_t number,
378 UnicodeString& appendTo,
379 FieldPosition& status) const
380{
381 return format((double) number, appendTo, status);
382}
383
b75a7d8f 384// -------------------------------------
4388f060
A
385// Formats an int32_t number, it's actually formatted as
386// a double.
b75a7d8f
A
387
388UnicodeString&
389ChoiceFormat::format(int32_t number,
390 UnicodeString& appendTo,
391 FieldPosition& status) const
392{
393 return format((double) number, appendTo, status);
394}
395
396// -------------------------------------
397// Formats a double number.
398
399UnicodeString&
400ChoiceFormat::format(double number,
401 UnicodeString& appendTo,
402 FieldPosition& /*pos*/) const
403{
4388f060
A
404 if (msgPattern.countParts() == 0) {
405 // No pattern was applied, or it failed.
406 return appendTo;
407 }
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(),
414 patternStart,
415 msgPattern.getPatternIndex(msgLimit) - patternStart);
416 return appendTo;
417 }
418 // JDK compatibility mode: Remove SKIP_SYNTAX.
419 return MessageImpl::appendSubMessageWithoutSkipSyntax(msgPattern, msgStart, appendTo);
420}
421
422int32_t
423ChoiceFormat::findSubMessage(const MessagePattern &pattern, int32_t partIndex, double number) {
424 int32_t count = pattern.countParts();
425 int32_t msgStart;
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.
429 partIndex += 2;
430 for (;;) {
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.
437 break;
438 }
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.
444 break;
445 }
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.
b75a7d8f
A
457 break;
458 }
459 }
4388f060 460 return msgStart;
b75a7d8f
A
461}
462
463// -------------------------------------
464// Formats an array of objects. Checks if the data type of the objects
465// to get the right value for formatting.
466
467UnicodeString&
468ChoiceFormat::format(const Formattable* objs,
469 int32_t cnt,
470 UnicodeString& appendTo,
471 FieldPosition& pos,
472 UErrorCode& status) const
473{
474 if(cnt < 0) {
475 status = U_ILLEGAL_ARGUMENT_ERROR;
476 return appendTo;
477 }
4388f060
A
478 if (msgPattern.countParts() == 0) {
479 status = U_INVALID_STATE_ERROR;
480 return appendTo;
481 }
b75a7d8f 482
b75a7d8f 483 for (int32_t i = 0; i < cnt; i++) {
374ca955
A
484 double objDouble = objs[i].getDouble(status);
485 if (U_SUCCESS(status)) {
4388f060 486 format(objDouble, appendTo, pos);
374ca955 487 }
b75a7d8f
A
488 }
489
490 return appendTo;
491}
492
493// -------------------------------------
494// Formats an array of objects. Checks if the data type of the objects
495// to get the right value for formatting.
496
497UnicodeString&
498ChoiceFormat::format(const Formattable& obj,
499 UnicodeString& appendTo,
500 FieldPosition& pos,
501 UErrorCode& status) const
502{
503 return NumberFormat::format(obj, appendTo, pos, status);
504}
505// -------------------------------------
506
507void
508ChoiceFormat::parse(const UnicodeString& text,
509 Formattable& result,
4388f060 510 ParsePosition& pos) const
b75a7d8f 511{
4388f060
A
512 result.setDouble(parseArgument(msgPattern, 0, text, pos));
513}
514
515double
516ChoiceFormat::parseArgument(
517 const MessagePattern &pattern, int32_t partIndex,
518 const UnicodeString &source, ParsePosition &pos) {
b75a7d8f 519 // find the best number (defined as the one with the longest parse)
4388f060 520 int32_t start = pos.getIndex();
b75a7d8f
A
521 int32_t furthest = start;
522 double bestNumber = uprv_getNaN();
523 double tempNumber = 0.0;
4388f060
A
524 int32_t count = pattern.countParts();
525 while (partIndex < count && pattern.getPartType(partIndex) != UMSGPAT_PART_TYPE_ARG_LIMIT) {
526 tempNumber = pattern.getNumericValue(pattern.getPart(partIndex));
527 partIndex += 2; // skip the numeric part and ignore the ARG_SELECTOR
528 int32_t msgLimit = pattern.getLimitPartIndex(partIndex);
529 int32_t len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start);
530 if (len >= 0) {
531 int32_t newIndex = start + len;
532 if (newIndex > furthest) {
533 furthest = newIndex;
b75a7d8f 534 bestNumber = tempNumber;
4388f060 535 if (furthest == source.length()) {
b75a7d8f 536 break;
4388f060 537 }
b75a7d8f
A
538 }
539 }
4388f060 540 partIndex = msgLimit + 1;
b75a7d8f 541 }
4388f060
A
542 if (furthest == start) {
543 pos.setErrorIndex(start);
544 } else {
545 pos.setIndex(furthest);
546 }
547 return bestNumber;
548}
549
550int32_t
551ChoiceFormat::matchStringUntilLimitPart(
552 const MessagePattern &pattern, int32_t partIndex, int32_t limitPartIndex,
553 const UnicodeString &source, int32_t sourceOffset) {
554 int32_t matchingSourceLength = 0;
555 const UnicodeString &msgString = pattern.getPatternString();
556 int32_t prevIndex = pattern.getPart(partIndex).getLimit();
557 for (;;) {
558 const MessagePattern::Part &part = pattern.getPart(++partIndex);
559 if (partIndex == limitPartIndex || part.getType() == UMSGPAT_PART_TYPE_SKIP_SYNTAX) {
560 int32_t index = part.getIndex();
561 int32_t length = index - prevIndex;
562 if (length != 0 && 0 != source.compare(sourceOffset, length, msgString, prevIndex, length)) {
563 return -1; // mismatch
564 }
565 matchingSourceLength += length;
566 if (partIndex == limitPartIndex) {
567 return matchingSourceLength;
568 }
569 prevIndex = part.getLimit(); // SKIP_SYNTAX
570 }
b75a7d8f 571 }
b75a7d8f
A
572}
573
574// -------------------------------------
575// Parses the text and return the Formattable object.
576
577void
578ChoiceFormat::parse(const UnicodeString& text,
579 Formattable& result,
580 UErrorCode& status) const
581{
582 NumberFormat::parse(text, result, status);
583}
584
585// -------------------------------------
586
587Format*
588ChoiceFormat::clone() const
589{
590 ChoiceFormat *aCopy = new ChoiceFormat(*this);
591 return aCopy;
592}
593
594U_NAMESPACE_END
595
596#endif /* #if !UCONFIG_NO_FORMATTING */
597
598//eof