Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/plurfmt.cpp
Line
Count
Source (jump to first uncovered line)
1
// © 2016 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
/*
4
*******************************************************************************
5
* Copyright (C) 2009-2015, International Business Machines Corporation and
6
* others. All Rights Reserved.
7
*******************************************************************************
8
*
9
* File PLURFMT.CPP
10
*******************************************************************************
11
*/
12
13
#include "unicode/decimfmt.h"
14
#include "unicode/messagepattern.h"
15
#include "unicode/plurfmt.h"
16
#include "unicode/plurrule.h"
17
#include "unicode/utypes.h"
18
#include "cmemory.h"
19
#include "messageimpl.h"
20
#include "nfrule.h"
21
#include "plurrule_impl.h"
22
#include "uassert.h"
23
#include "uhash.h"
24
#include "number_decimalquantity.h"
25
#include "number_utils.h"
26
#include "number_utypes.h"
27
28
#if !UCONFIG_NO_FORMATTING
29
30
U_NAMESPACE_BEGIN
31
32
using number::impl::DecimalQuantity;
33
34
static const UChar OTHER_STRING[] = {
35
    0x6F, 0x74, 0x68, 0x65, 0x72, 0  // "other"
36
};
37
38
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralFormat)
39
40
PluralFormat::PluralFormat(UErrorCode& status)
41
0
        : locale(Locale::getDefault()),
42
0
          msgPattern(status),
43
          numberFormat(NULL),
44
0
          offset(0) {
45
0
    init(NULL, UPLURAL_TYPE_CARDINAL, status);
46
0
}
47
48
PluralFormat::PluralFormat(const Locale& loc, UErrorCode& status)
49
0
        : locale(loc),
50
0
          msgPattern(status),
51
          numberFormat(NULL),
52
0
          offset(0) {
53
0
    init(NULL, UPLURAL_TYPE_CARDINAL, status);
54
0
}
55
56
PluralFormat::PluralFormat(const PluralRules& rules, UErrorCode& status)
57
0
        : locale(Locale::getDefault()),
58
0
          msgPattern(status),
59
          numberFormat(NULL),
60
0
          offset(0) {
61
0
    init(&rules, UPLURAL_TYPE_COUNT, status);
62
0
}
63
64
PluralFormat::PluralFormat(const Locale& loc,
65
                           const PluralRules& rules,
66
                           UErrorCode& status)
67
0
        : locale(loc),
68
0
          msgPattern(status),
69
          numberFormat(NULL),
70
0
          offset(0) {
71
0
    init(&rules, UPLURAL_TYPE_COUNT, status);
72
0
}
73
74
PluralFormat::PluralFormat(const Locale& loc,
75
                           UPluralType type,
76
                           UErrorCode& status)
77
0
        : locale(loc),
78
0
          msgPattern(status),
79
          numberFormat(NULL),
80
0
          offset(0) {
81
0
    init(NULL, type, status);
82
0
}
83
84
PluralFormat::PluralFormat(const UnicodeString& pat,
85
                           UErrorCode& status)
86
0
        : locale(Locale::getDefault()),
87
0
          msgPattern(status),
88
          numberFormat(NULL),
89
0
          offset(0) {
90
0
    init(NULL, UPLURAL_TYPE_CARDINAL, status);
91
0
    applyPattern(pat, status);
92
0
}
93
94
PluralFormat::PluralFormat(const Locale& loc,
95
                           const UnicodeString& pat,
96
                           UErrorCode& status)
97
0
        : locale(loc),
98
0
          msgPattern(status),
99
          numberFormat(NULL),
100
0
          offset(0) {
101
0
    init(NULL, UPLURAL_TYPE_CARDINAL, status);
102
0
    applyPattern(pat, status);
103
0
}
104
105
PluralFormat::PluralFormat(const PluralRules& rules,
106
                           const UnicodeString& pat,
107
                           UErrorCode& status)
108
0
        : locale(Locale::getDefault()),
109
0
          msgPattern(status),
110
          numberFormat(NULL),
111
0
          offset(0) {
112
0
    init(&rules, UPLURAL_TYPE_COUNT, status);
113
0
    applyPattern(pat, status);
114
0
}
115
116
PluralFormat::PluralFormat(const Locale& loc,
117
                           const PluralRules& rules,
118
                           const UnicodeString& pat,
119
                           UErrorCode& status)
120
0
        : locale(loc),
121
0
          msgPattern(status),
122
          numberFormat(NULL),
123
0
          offset(0) {
124
0
    init(&rules, UPLURAL_TYPE_COUNT, status);
125
0
    applyPattern(pat, status);
126
0
}
127
128
PluralFormat::PluralFormat(const Locale& loc,
129
                           UPluralType type,
130
                           const UnicodeString& pat,
131
                           UErrorCode& status)
132
0
        : locale(loc),
133
0
          msgPattern(status),
134
          numberFormat(NULL),
135
0
          offset(0) {
136
0
    init(NULL, type, status);
137
0
    applyPattern(pat, status);
138
0
}
139
140
PluralFormat::PluralFormat(const PluralFormat& other)
141
0
        : Format(other),
142
0
          locale(other.locale),
143
0
          msgPattern(other.msgPattern),
144
          numberFormat(NULL),
145
0
          offset(other.offset) {
146
0
    copyObjects(other);
147
0
}
148
149
void
150
0
PluralFormat::copyObjects(const PluralFormat& other) {
151
0
    UErrorCode status = U_ZERO_ERROR;
152
0
    if (numberFormat != NULL) {
153
0
        delete numberFormat;
154
0
    }
155
0
    if (pluralRulesWrapper.pluralRules != NULL) {
156
0
        delete pluralRulesWrapper.pluralRules;
157
0
    }
158
159
0
    if (other.numberFormat == NULL) {
160
0
        numberFormat = NumberFormat::createInstance(locale, status);
161
0
    } else {
162
0
        numberFormat = other.numberFormat->clone();
163
0
    }
164
0
    if (other.pluralRulesWrapper.pluralRules == NULL) {
165
0
        pluralRulesWrapper.pluralRules = PluralRules::forLocale(locale, status);
166
0
    } else {
167
0
        pluralRulesWrapper.pluralRules = other.pluralRulesWrapper.pluralRules->clone();
168
0
    }
169
0
}
170
171
172
0
PluralFormat::~PluralFormat() {
173
0
    delete numberFormat;
174
0
}
175
176
void
177
0
PluralFormat::init(const PluralRules* rules, UPluralType type, UErrorCode& status) {
178
0
    if (U_FAILURE(status)) {
179
0
        return;
180
0
    }
181
182
0
    if (rules==NULL) {
183
0
        pluralRulesWrapper.pluralRules = PluralRules::forLocale(locale, type, status);
184
0
    } else {
185
0
        pluralRulesWrapper.pluralRules = rules->clone();
186
0
        if (pluralRulesWrapper.pluralRules == NULL) {
187
0
            status = U_MEMORY_ALLOCATION_ERROR;
188
0
            return;
189
0
        }
190
0
    }
191
192
0
    numberFormat= NumberFormat::createInstance(locale, status);
193
0
}
194
195
void
196
0
PluralFormat::applyPattern(const UnicodeString& newPattern, UErrorCode& status) {
197
0
    msgPattern.parsePluralStyle(newPattern, NULL, status);
198
0
    if (U_FAILURE(status)) {
199
0
        msgPattern.clear();
200
0
        offset = 0;
201
0
        return;
202
0
    }
203
0
    offset = msgPattern.getPluralOffset(0);
204
0
}
205
206
UnicodeString&
207
PluralFormat::format(const Formattable& obj,
208
                   UnicodeString& appendTo,
209
                   FieldPosition& pos,
210
                   UErrorCode& status) const
211
0
{
212
0
    if (U_FAILURE(status)) return appendTo;
213
214
0
    if (obj.isNumeric()) {
215
0
        return format(obj, obj.getDouble(), appendTo, pos, status);
216
0
    } else {
217
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
218
0
        return appendTo;
219
0
    }
220
0
}
221
222
UnicodeString
223
0
PluralFormat::format(int32_t number, UErrorCode& status) const {
224
0
    FieldPosition fpos(FieldPosition::DONT_CARE);
225
0
    UnicodeString result;
226
0
    return format(Formattable(number), number, result, fpos, status);
227
0
}
228
229
UnicodeString
230
0
PluralFormat::format(double number, UErrorCode& status) const {
231
0
    FieldPosition fpos(FieldPosition::DONT_CARE);
232
0
    UnicodeString result;
233
0
    return format(Formattable(number), number, result, fpos, status);
234
0
}
235
236
237
UnicodeString&
238
PluralFormat::format(int32_t number,
239
                     UnicodeString& appendTo,
240
                     FieldPosition& pos,
241
0
                     UErrorCode& status) const {
242
0
    return format(Formattable(number), (double)number, appendTo, pos, status);
243
0
}
244
245
UnicodeString&
246
PluralFormat::format(double number,
247
                     UnicodeString& appendTo,
248
                     FieldPosition& pos,
249
0
                     UErrorCode& status) const {
250
0
    return format(Formattable(number), (double)number, appendTo, pos, status);
251
0
}
252
253
UnicodeString&
254
PluralFormat::format(const Formattable& numberObject, double number,
255
                     UnicodeString& appendTo,
256
                     FieldPosition& pos,
257
0
                     UErrorCode& status) const {
258
0
    if (U_FAILURE(status)) {
259
0
        return appendTo;
260
0
    }
261
0
    if (msgPattern.countParts() == 0) {
262
0
        return numberFormat->format(numberObject, appendTo, pos, status);
263
0
    }
264
265
    // Get the appropriate sub-message.
266
    // Select it based on the formatted number-offset.
267
0
    double numberMinusOffset = number - offset;
268
    // Call NumberFormatter to get both the DecimalQuantity and the string.
269
    // This call site needs to use more internal APIs than the Java equivalent.
270
0
    number::impl::UFormattedNumberData data;
271
0
    if (offset == 0) {
272
        // could be BigDecimal etc.
273
0
        numberObject.populateDecimalQuantity(data.quantity, status);
274
0
    } else {
275
0
        data.quantity.setToDouble(numberMinusOffset);
276
0
    }
277
0
    UnicodeString numberString;
278
0
    auto *decFmt = dynamic_cast<DecimalFormat *>(numberFormat);
279
0
    if(decFmt != nullptr) {
280
0
        const number::LocalizedNumberFormatter* lnf = decFmt->toNumberFormatter(status);
281
0
        if (U_FAILURE(status)) {
282
0
            return appendTo;
283
0
        }
284
0
        lnf->formatImpl(&data, status); // mutates &data
285
0
        if (U_FAILURE(status)) {
286
0
            return appendTo;
287
0
        }
288
0
        numberString = data.getStringRef().toUnicodeString();
289
0
    } else {
290
0
        if (offset == 0) {
291
0
            numberFormat->format(numberObject, numberString, status);
292
0
        } else {
293
0
            numberFormat->format(numberMinusOffset, numberString, status);
294
0
        }
295
0
    }
296
297
0
    int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &data.quantity, number, status);
298
0
    if (U_FAILURE(status)) { return appendTo; }
299
    // Replace syntactic # signs in the top level of this sub-message
300
    // (not in nested arguments) with the formatted number-offset.
301
0
    const UnicodeString& pattern = msgPattern.getPatternString();
302
0
    int32_t prevIndex = msgPattern.getPart(partIndex).getLimit();
303
0
    for (;;) {
304
0
        const MessagePattern::Part& part = msgPattern.getPart(++partIndex);
305
0
        const UMessagePatternPartType type = part.getType();
306
0
        int32_t index = part.getIndex();
307
0
        if (type == UMSGPAT_PART_TYPE_MSG_LIMIT) {
308
0
            return appendTo.append(pattern, prevIndex, index - prevIndex);
309
0
        } else if ((type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) ||
310
0
            (type == UMSGPAT_PART_TYPE_SKIP_SYNTAX && MessageImpl::jdkAposMode(msgPattern))) {
311
0
            appendTo.append(pattern, prevIndex, index - prevIndex);
312
0
            if (type == UMSGPAT_PART_TYPE_REPLACE_NUMBER) {
313
0
                appendTo.append(numberString);
314
0
            }
315
0
            prevIndex = part.getLimit();
316
0
        } else if (type == UMSGPAT_PART_TYPE_ARG_START) {
317
0
            appendTo.append(pattern, prevIndex, index - prevIndex);
318
0
            prevIndex = index;
319
0
            partIndex = msgPattern.getLimitPartIndex(partIndex);
320
0
            index = msgPattern.getPart(partIndex).getLimit();
321
0
            MessageImpl::appendReducedApostrophes(pattern, prevIndex, index, appendTo);
322
0
            prevIndex = index;
323
0
        }
324
0
    }
325
0
}
326
327
UnicodeString&
328
0
PluralFormat::toPattern(UnicodeString& appendTo) {
329
0
    if (0 == msgPattern.countParts()) {
330
0
        appendTo.setToBogus();
331
0
    } else {
332
0
        appendTo.append(msgPattern.getPatternString());
333
0
    }
334
0
    return appendTo;
335
0
}
336
337
void
338
0
PluralFormat::setLocale(const Locale& loc, UErrorCode& status) {
339
0
    if (U_FAILURE(status)) {
340
0
        return;
341
0
    }
342
0
    locale = loc;
343
0
    msgPattern.clear();
344
0
    delete numberFormat;
345
0
    offset = 0;
346
0
    numberFormat = NULL;
347
0
    pluralRulesWrapper.reset();
348
0
    init(NULL, UPLURAL_TYPE_CARDINAL, status);
349
0
}
350
351
void
352
0
PluralFormat::setNumberFormat(const NumberFormat* format, UErrorCode& status) {
353
0
    if (U_FAILURE(status)) {
354
0
        return;
355
0
    }
356
0
    NumberFormat* nf = format->clone();
357
0
    if (nf != NULL) {
358
0
        delete numberFormat;
359
0
        numberFormat = nf;
360
0
    } else {
361
0
        status = U_MEMORY_ALLOCATION_ERROR;
362
0
    }
363
0
}
364
365
PluralFormat*
366
PluralFormat::clone() const
367
0
{
368
0
    return new PluralFormat(*this);
369
0
}
370
371
372
PluralFormat&
373
0
PluralFormat::operator=(const PluralFormat& other) {
374
0
    if (this != &other) {
375
0
        locale = other.locale;
376
0
        msgPattern = other.msgPattern;
377
0
        offset = other.offset;
378
0
        copyObjects(other);
379
0
    }
380
381
0
    return *this;
382
0
}
383
384
bool
385
0
PluralFormat::operator==(const Format& other) const {
386
0
    if (this == &other) {
387
0
        return TRUE;
388
0
    }
389
0
    if (!Format::operator==(other)) {
390
0
        return FALSE;
391
0
    }
392
0
    const PluralFormat& o = (const PluralFormat&)other;
393
0
    return
394
0
        locale == o.locale &&
395
0
        msgPattern == o.msgPattern &&  // implies same offset
396
0
        (numberFormat == NULL) == (o.numberFormat == NULL) &&
397
0
        (numberFormat == NULL || *numberFormat == *o.numberFormat) &&
398
0
        (pluralRulesWrapper.pluralRules == NULL) == (o.pluralRulesWrapper.pluralRules == NULL) &&
399
0
        (pluralRulesWrapper.pluralRules == NULL ||
400
0
            *pluralRulesWrapper.pluralRules == *o.pluralRulesWrapper.pluralRules);
401
0
}
402
403
bool
404
0
PluralFormat::operator!=(const Format& other) const {
405
0
    return  !operator==(other);
406
0
}
407
408
void
409
PluralFormat::parseObject(const UnicodeString& /*source*/,
410
                        Formattable& /*result*/,
411
                        ParsePosition& pos) const
412
0
{
413
    // Parsing not supported.
414
0
    pos.setErrorIndex(pos.getIndex());
415
0
}
416
417
int32_t PluralFormat::findSubMessage(const MessagePattern& pattern, int32_t partIndex,
418
                                     const PluralSelector& selector, void *context,
419
0
                                     double number, UErrorCode& ec) {
420
0
    if (U_FAILURE(ec)) {
421
0
        return 0;
422
0
    }
423
0
    int32_t count=pattern.countParts();
424
0
    double offset;
425
0
    const MessagePattern::Part* part=&pattern.getPart(partIndex);
426
0
    if (MessagePattern::Part::hasNumericValue(part->getType())) {
427
0
        offset=pattern.getNumericValue(*part);
428
0
        ++partIndex;
429
0
    } else {
430
0
        offset=0;
431
0
    }
432
    // The keyword is empty until we need to match against a non-explicit, not-"other" value.
433
    // Then we get the keyword from the selector.
434
    // (In other words, we never call the selector if we match against an explicit value,
435
    // or if the only non-explicit keyword is "other".)
436
0
    UnicodeString keyword;
437
0
    UnicodeString other(FALSE, OTHER_STRING, 5);
438
    // When we find a match, we set msgStart>0 and also set this boolean to true
439
    // to avoid matching the keyword again (duplicates are allowed)
440
    // while we continue to look for an explicit-value match.
441
0
    UBool haveKeywordMatch=FALSE;
442
    // msgStart is 0 until we find any appropriate sub-message.
443
    // We remember the first "other" sub-message if we have not seen any
444
    // appropriate sub-message before.
445
    // We remember the first matching-keyword sub-message if we have not seen
446
    // one of those before.
447
    // (The parser allows [does not check for] duplicate keywords.
448
    // We just have to make sure to take the first one.)
449
    // We avoid matching the keyword twice by also setting haveKeywordMatch=true
450
    // at the first keyword match.
451
    // We keep going until we find an explicit-value match or reach the end of the plural style.
452
0
    int32_t msgStart=0;
453
    // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
454
    // until ARG_LIMIT or end of plural-only pattern.
455
0
    do {
456
0
        part=&pattern.getPart(partIndex++);
457
0
        const UMessagePatternPartType type = part->getType();
458
0
        if(type==UMSGPAT_PART_TYPE_ARG_LIMIT) {
459
0
            break;
460
0
        }
461
0
        U_ASSERT (type==UMSGPAT_PART_TYPE_ARG_SELECTOR);
462
        // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
463
0
        if(MessagePattern::Part::hasNumericValue(pattern.getPartType(partIndex))) {
464
            // explicit value like "=2"
465
0
            part=&pattern.getPart(partIndex++);
466
0
            if(number==pattern.getNumericValue(*part)) {
467
                // matches explicit value
468
0
                return partIndex;
469
0
            }
470
0
        } else if(!haveKeywordMatch) {
471
            // plural keyword like "few" or "other"
472
            // Compare "other" first and call the selector if this is not "other".
473
0
            if(pattern.partSubstringMatches(*part, other)) {
474
0
                if(msgStart==0) {
475
0
                    msgStart=partIndex;
476
0
                    if(0 == keyword.compare(other)) {
477
                        // This is the first "other" sub-message,
478
                        // and the selected keyword is also "other".
479
                        // Do not match "other" again.
480
0
                        haveKeywordMatch=TRUE;
481
0
                    }
482
0
                }
483
0
            } else {
484
0
                if(keyword.isEmpty()) {
485
0
                    keyword=selector.select(context, number-offset, ec);
486
0
                    if(msgStart!=0 && (0 == keyword.compare(other))) {
487
                        // We have already seen an "other" sub-message.
488
                        // Do not match "other" again.
489
0
                        haveKeywordMatch=TRUE;
490
                        // Skip keyword matching but do getLimitPartIndex().
491
0
                    }
492
0
                }
493
0
                if(!haveKeywordMatch && pattern.partSubstringMatches(*part, keyword)) {
494
                    // keyword matches
495
0
                    msgStart=partIndex;
496
                    // Do not match this keyword again.
497
0
                    haveKeywordMatch=TRUE;
498
0
                }
499
0
            }
500
0
        }
501
0
        partIndex=pattern.getLimitPartIndex(partIndex);
502
0
    } while(++partIndex<count);
503
0
    return msgStart;
504
0
}
505
506
0
void PluralFormat::parseType(const UnicodeString& source, const NFRule *rbnfLenientScanner, Formattable& result, FieldPosition& pos) const {
507
    // If no pattern was applied, return null.
508
0
    if (msgPattern.countParts() == 0) {
509
0
        pos.setBeginIndex(-1);
510
0
        pos.setEndIndex(-1);
511
0
        return;
512
0
    }
513
0
    int partIndex = 0;
514
0
    int currMatchIndex;
515
0
    int count=msgPattern.countParts();
516
0
    int startingAt = pos.getBeginIndex();
517
0
    if (startingAt < 0) {
518
0
        startingAt = 0;
519
0
    }
520
521
    // The keyword is null until we need to match against a non-explicit, not-"other" value.
522
    // Then we get the keyword from the selector.
523
    // (In other words, we never call the selector if we match against an explicit value,
524
    // or if the only non-explicit keyword is "other".)
525
0
    UnicodeString keyword;
526
0
    UnicodeString matchedWord;
527
0
    const UnicodeString& pattern = msgPattern.getPatternString();
528
0
    int matchedIndex = -1;
529
    // Iterate over (ARG_SELECTOR ARG_START message ARG_LIMIT) tuples
530
    // until the end of the plural-only pattern.
531
0
    while (partIndex < count) {
532
0
        const MessagePattern::Part* partSelector = &msgPattern.getPart(partIndex++);
533
0
        if (partSelector->getType() != UMSGPAT_PART_TYPE_ARG_SELECTOR) {
534
            // Bad format
535
0
            continue;
536
0
        }
537
538
0
        const MessagePattern::Part* partStart = &msgPattern.getPart(partIndex++);
539
0
        if (partStart->getType() != UMSGPAT_PART_TYPE_MSG_START) {
540
            // Bad format
541
0
            continue;
542
0
        }
543
544
0
        const MessagePattern::Part* partLimit = &msgPattern.getPart(partIndex++);
545
0
        if (partLimit->getType() != UMSGPAT_PART_TYPE_MSG_LIMIT) {
546
            // Bad format
547
0
            continue;
548
0
        }
549
550
0
        UnicodeString currArg = pattern.tempSubString(partStart->getLimit(), partLimit->getIndex() - partStart->getLimit());
551
0
        if (rbnfLenientScanner != NULL) {
552
            // Check if non-lenient rule finds the text before call lenient parsing
553
0
            int32_t tempIndex = source.indexOf(currArg, startingAt);
554
0
            if (tempIndex >= 0) {
555
0
                currMatchIndex = tempIndex;
556
0
            } else {
557
                // If lenient parsing is turned ON, we've got some time consuming parsing ahead of us.
558
0
                int32_t length = -1;
559
0
                currMatchIndex = rbnfLenientScanner->findTextLenient(source, currArg, startingAt, &length);
560
0
            }
561
0
        }
562
0
        else {
563
0
            currMatchIndex = source.indexOf(currArg, startingAt);
564
0
        }
565
0
        if (currMatchIndex >= 0 && currMatchIndex >= matchedIndex && currArg.length() > matchedWord.length()) {
566
0
            matchedIndex = currMatchIndex;
567
0
            matchedWord = currArg;
568
0
            keyword = pattern.tempSubString(partStart->getLimit(), partLimit->getIndex() - partStart->getLimit());
569
0
        }
570
0
    }
571
0
    if (matchedIndex >= 0) {
572
0
        pos.setBeginIndex(matchedIndex);
573
0
        pos.setEndIndex(matchedIndex + matchedWord.length());
574
0
        result.setString(keyword);
575
0
        return;
576
0
    }
577
578
    // Not found!
579
0
    pos.setBeginIndex(-1);
580
0
    pos.setEndIndex(-1);
581
0
}
582
583
0
PluralFormat::PluralSelector::~PluralSelector() {}
584
585
0
PluralFormat::PluralSelectorAdapter::~PluralSelectorAdapter() {
586
0
    delete pluralRules;
587
0
}
588
589
UnicodeString PluralFormat::PluralSelectorAdapter::select(void *context, double number,
590
0
                                                          UErrorCode& /*ec*/) const {
591
0
    (void)number;  // unused except in the assertion
592
0
    IFixedDecimal *dec=static_cast<IFixedDecimal *>(context);
593
0
    return pluralRules->select(*dec);
594
0
}
595
596
0
void PluralFormat::PluralSelectorAdapter::reset() {
597
0
    delete pluralRules;
598
0
    pluralRules = NULL;
599
0
}
600
601
602
U_NAMESPACE_END
603
604
605
#endif /* #if !UCONFIG_NO_FORMATTING */
606
607
//eof