/src/mozilla-central/intl/icu/source/i18n/number_stringbuilder.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // © 2017 and later: Unicode, Inc. and others. |
2 | | // License & terms of use: http://www.unicode.org/copyright.html |
3 | | |
4 | | #include "unicode/utypes.h" |
5 | | |
6 | | #if !UCONFIG_NO_FORMATTING |
7 | | |
8 | | #include "number_stringbuilder.h" |
9 | | #include "unicode/utf16.h" |
10 | | |
11 | | using namespace icu; |
12 | | using namespace icu::number; |
13 | | using namespace icu::number::impl; |
14 | | |
15 | | namespace { |
16 | | |
17 | | // A version of uprv_memcpy that checks for length 0. |
18 | | // By default, uprv_memcpy requires a length of at least 1. |
19 | 0 | inline void uprv_memcpy2(void* dest, const void* src, size_t len) { |
20 | 0 | if (len > 0) { |
21 | 0 | uprv_memcpy(dest, src, len); |
22 | 0 | } |
23 | 0 | } |
24 | | |
25 | | // A version of uprv_memmove that checks for length 0. |
26 | | // By default, uprv_memmove requires a length of at least 1. |
27 | 0 | inline void uprv_memmove2(void* dest, const void* src, size_t len) { |
28 | 0 | if (len > 0) { |
29 | 0 | uprv_memmove(dest, src, len); |
30 | 0 | } |
31 | 0 | } |
32 | | |
33 | | } // namespace |
34 | | |
35 | 0 | NumberStringBuilder::NumberStringBuilder() = default; |
36 | | |
37 | 0 | NumberStringBuilder::~NumberStringBuilder() { |
38 | 0 | if (fUsingHeap) { |
39 | 0 | uprv_free(fChars.heap.ptr); |
40 | 0 | uprv_free(fFields.heap.ptr); |
41 | 0 | } |
42 | 0 | } |
43 | | |
44 | 0 | NumberStringBuilder::NumberStringBuilder(const NumberStringBuilder &other) { |
45 | 0 | *this = other; |
46 | 0 | } |
47 | | |
48 | 0 | NumberStringBuilder &NumberStringBuilder::operator=(const NumberStringBuilder &other) { |
49 | 0 | // Check for self-assignment |
50 | 0 | if (this == &other) { |
51 | 0 | return *this; |
52 | 0 | } |
53 | 0 | |
54 | 0 | // Continue with deallocation and copying |
55 | 0 | if (fUsingHeap) { |
56 | 0 | uprv_free(fChars.heap.ptr); |
57 | 0 | uprv_free(fFields.heap.ptr); |
58 | 0 | fUsingHeap = false; |
59 | 0 | } |
60 | 0 |
|
61 | 0 | int32_t capacity = other.getCapacity(); |
62 | 0 | if (capacity > DEFAULT_CAPACITY) { |
63 | 0 | // FIXME: uprv_malloc |
64 | 0 | // C++ note: malloc appears in two places: here and in prepareForInsertHelper. |
65 | 0 | auto newChars = static_cast<char16_t *> (uprv_malloc(sizeof(char16_t) * capacity)); |
66 | 0 | auto newFields = static_cast<Field *>(uprv_malloc(sizeof(Field) * capacity)); |
67 | 0 | if (newChars == nullptr || newFields == nullptr) { |
68 | 0 | // UErrorCode is not available; fail silently. |
69 | 0 | uprv_free(newChars); |
70 | 0 | uprv_free(newFields); |
71 | 0 | *this = NumberStringBuilder(); // can't fail |
72 | 0 | return *this; |
73 | 0 | } |
74 | 0 |
|
75 | 0 | fUsingHeap = true; |
76 | 0 | fChars.heap.capacity = capacity; |
77 | 0 | fChars.heap.ptr = newChars; |
78 | 0 | fFields.heap.capacity = capacity; |
79 | 0 | fFields.heap.ptr = newFields; |
80 | 0 | } |
81 | 0 |
|
82 | 0 | uprv_memcpy2(getCharPtr(), other.getCharPtr(), sizeof(char16_t) * capacity); |
83 | 0 | uprv_memcpy2(getFieldPtr(), other.getFieldPtr(), sizeof(Field) * capacity); |
84 | 0 |
|
85 | 0 | fZero = other.fZero; |
86 | 0 | fLength = other.fLength; |
87 | 0 | return *this; |
88 | 0 | } |
89 | | |
90 | 0 | int32_t NumberStringBuilder::length() const { |
91 | 0 | return fLength; |
92 | 0 | } |
93 | | |
94 | 0 | int32_t NumberStringBuilder::codePointCount() const { |
95 | 0 | return u_countChar32(getCharPtr() + fZero, fLength); |
96 | 0 | } |
97 | | |
98 | 0 | UChar32 NumberStringBuilder::getFirstCodePoint() const { |
99 | 0 | if (fLength == 0) { |
100 | 0 | return -1; |
101 | 0 | } |
102 | 0 | UChar32 cp; |
103 | 0 | U16_GET(getCharPtr() + fZero, 0, 0, fLength, cp); |
104 | 0 | return cp; |
105 | 0 | } |
106 | | |
107 | 0 | UChar32 NumberStringBuilder::getLastCodePoint() const { |
108 | 0 | if (fLength == 0) { |
109 | 0 | return -1; |
110 | 0 | } |
111 | 0 | int32_t offset = fLength; |
112 | 0 | U16_BACK_1(getCharPtr() + fZero, 0, offset); |
113 | 0 | UChar32 cp; |
114 | 0 | U16_GET(getCharPtr() + fZero, 0, offset, fLength, cp); |
115 | 0 | return cp; |
116 | 0 | } |
117 | | |
118 | 0 | UChar32 NumberStringBuilder::codePointAt(int32_t index) const { |
119 | 0 | UChar32 cp; |
120 | 0 | U16_GET(getCharPtr() + fZero, 0, index, fLength, cp); |
121 | 0 | return cp; |
122 | 0 | } |
123 | | |
124 | 0 | UChar32 NumberStringBuilder::codePointBefore(int32_t index) const { |
125 | 0 | int32_t offset = index; |
126 | 0 | U16_BACK_1(getCharPtr() + fZero, 0, offset); |
127 | 0 | UChar32 cp; |
128 | 0 | U16_GET(getCharPtr() + fZero, 0, offset, fLength, cp); |
129 | 0 | return cp; |
130 | 0 | } |
131 | | |
132 | 0 | NumberStringBuilder &NumberStringBuilder::clear() { |
133 | 0 | // TODO: Reset the heap here? |
134 | 0 | fZero = getCapacity() / 2; |
135 | 0 | fLength = 0; |
136 | 0 | return *this; |
137 | 0 | } |
138 | | |
139 | 0 | int32_t NumberStringBuilder::appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status) { |
140 | 0 | return insertCodePoint(fLength, codePoint, field, status); |
141 | 0 | } |
142 | | |
143 | | int32_t |
144 | 0 | NumberStringBuilder::insertCodePoint(int32_t index, UChar32 codePoint, Field field, UErrorCode &status) { |
145 | 0 | int32_t count = U16_LENGTH(codePoint); |
146 | 0 | int32_t position = prepareForInsert(index, count, status); |
147 | 0 | if (U_FAILURE(status)) { |
148 | 0 | return count; |
149 | 0 | } |
150 | 0 | if (count == 1) { |
151 | 0 | getCharPtr()[position] = (char16_t) codePoint; |
152 | 0 | getFieldPtr()[position] = field; |
153 | 0 | } else { |
154 | 0 | getCharPtr()[position] = U16_LEAD(codePoint); |
155 | 0 | getCharPtr()[position + 1] = U16_TRAIL(codePoint); |
156 | 0 | getFieldPtr()[position] = getFieldPtr()[position + 1] = field; |
157 | 0 | } |
158 | 0 | return count; |
159 | 0 | } |
160 | | |
161 | 0 | int32_t NumberStringBuilder::append(const UnicodeString &unistr, Field field, UErrorCode &status) { |
162 | 0 | return insert(fLength, unistr, field, status); |
163 | 0 | } |
164 | | |
165 | | int32_t NumberStringBuilder::insert(int32_t index, const UnicodeString &unistr, Field field, |
166 | 0 | UErrorCode &status) { |
167 | 0 | if (unistr.length() == 0) { |
168 | 0 | // Nothing to insert. |
169 | 0 | return 0; |
170 | 0 | } else if (unistr.length() == 1) { |
171 | 0 | // Fast path: insert using insertCodePoint. |
172 | 0 | return insertCodePoint(index, unistr.charAt(0), field, status); |
173 | 0 | } else { |
174 | 0 | return insert(index, unistr, 0, unistr.length(), field, status); |
175 | 0 | } |
176 | 0 | } |
177 | | |
178 | | int32_t |
179 | | NumberStringBuilder::insert(int32_t index, const UnicodeString &unistr, int32_t start, int32_t end, |
180 | 0 | Field field, UErrorCode &status) { |
181 | 0 | int32_t count = end - start; |
182 | 0 | int32_t position = prepareForInsert(index, count, status); |
183 | 0 | if (U_FAILURE(status)) { |
184 | 0 | return count; |
185 | 0 | } |
186 | 0 | for (int32_t i = 0; i < count; i++) { |
187 | 0 | getCharPtr()[position + i] = unistr.charAt(start + i); |
188 | 0 | getFieldPtr()[position + i] = field; |
189 | 0 | } |
190 | 0 | return count; |
191 | 0 | } |
192 | | |
193 | | int32_t |
194 | | NumberStringBuilder::splice(int32_t startThis, int32_t endThis, const UnicodeString &unistr, |
195 | 0 | int32_t startOther, int32_t endOther, Field field, UErrorCode& status) { |
196 | 0 | int32_t thisLength = endThis - startThis; |
197 | 0 | int32_t otherLength = endOther - startOther; |
198 | 0 | int32_t count = otherLength - thisLength; |
199 | 0 | int32_t position; |
200 | 0 | if (count > 0) { |
201 | 0 | // Overall, chars need to be added. |
202 | 0 | position = prepareForInsert(startThis, count, status); |
203 | 0 | } else { |
204 | 0 | // Overall, chars need to be removed or kept the same. |
205 | 0 | position = remove(startThis, -count); |
206 | 0 | } |
207 | 0 | if (U_FAILURE(status)) { |
208 | 0 | return count; |
209 | 0 | } |
210 | 0 | for (int32_t i = 0; i < otherLength; i++) { |
211 | 0 | getCharPtr()[position + i] = unistr.charAt(startOther + i); |
212 | 0 | getFieldPtr()[position + i] = field; |
213 | 0 | } |
214 | 0 | return count; |
215 | 0 | } |
216 | | |
217 | 0 | int32_t NumberStringBuilder::append(const NumberStringBuilder &other, UErrorCode &status) { |
218 | 0 | return insert(fLength, other, status); |
219 | 0 | } |
220 | | |
221 | | int32_t |
222 | 0 | NumberStringBuilder::insert(int32_t index, const NumberStringBuilder &other, UErrorCode &status) { |
223 | 0 | if (this == &other) { |
224 | 0 | status = U_ILLEGAL_ARGUMENT_ERROR; |
225 | 0 | return 0; |
226 | 0 | } |
227 | 0 | int32_t count = other.fLength; |
228 | 0 | if (count == 0) { |
229 | 0 | // Nothing to insert. |
230 | 0 | return 0; |
231 | 0 | } |
232 | 0 | int32_t position = prepareForInsert(index, count, status); |
233 | 0 | if (U_FAILURE(status)) { |
234 | 0 | return count; |
235 | 0 | } |
236 | 0 | for (int32_t i = 0; i < count; i++) { |
237 | 0 | getCharPtr()[position + i] = other.charAt(i); |
238 | 0 | getFieldPtr()[position + i] = other.fieldAt(i); |
239 | 0 | } |
240 | 0 | return count; |
241 | 0 | } |
242 | | |
243 | 0 | int32_t NumberStringBuilder::prepareForInsert(int32_t index, int32_t count, UErrorCode &status) { |
244 | 0 | if (index == 0 && fZero - count >= 0) { |
245 | 0 | // Append to start |
246 | 0 | fZero -= count; |
247 | 0 | fLength += count; |
248 | 0 | return fZero; |
249 | 0 | } else if (index == fLength && fZero + fLength + count < getCapacity()) { |
250 | 0 | // Append to end |
251 | 0 | fLength += count; |
252 | 0 | return fZero + fLength - count; |
253 | 0 | } else { |
254 | 0 | // Move chars around and/or allocate more space |
255 | 0 | return prepareForInsertHelper(index, count, status); |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | 0 | int32_t NumberStringBuilder::prepareForInsertHelper(int32_t index, int32_t count, UErrorCode &status) { |
260 | 0 | int32_t oldCapacity = getCapacity(); |
261 | 0 | int32_t oldZero = fZero; |
262 | 0 | char16_t *oldChars = getCharPtr(); |
263 | 0 | Field *oldFields = getFieldPtr(); |
264 | 0 | if (fLength + count > oldCapacity) { |
265 | 0 | int32_t newCapacity = (fLength + count) * 2; |
266 | 0 | int32_t newZero = newCapacity / 2 - (fLength + count) / 2; |
267 | 0 |
|
268 | 0 | // C++ note: malloc appears in two places: here and in the assignment operator. |
269 | 0 | auto newChars = static_cast<char16_t *> (uprv_malloc(sizeof(char16_t) * newCapacity)); |
270 | 0 | auto newFields = static_cast<Field *>(uprv_malloc(sizeof(Field) * newCapacity)); |
271 | 0 | if (newChars == nullptr || newFields == nullptr) { |
272 | 0 | uprv_free(newChars); |
273 | 0 | uprv_free(newFields); |
274 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
275 | 0 | return -1; |
276 | 0 | } |
277 | 0 |
|
278 | 0 | // First copy the prefix and then the suffix, leaving room for the new chars that the |
279 | 0 | // caller wants to insert. |
280 | 0 | // C++ note: memcpy is OK because the src and dest do not overlap. |
281 | 0 | uprv_memcpy2(newChars + newZero, oldChars + oldZero, sizeof(char16_t) * index); |
282 | 0 | uprv_memcpy2(newChars + newZero + index + count, |
283 | 0 | oldChars + oldZero + index, |
284 | 0 | sizeof(char16_t) * (fLength - index)); |
285 | 0 | uprv_memcpy2(newFields + newZero, oldFields + oldZero, sizeof(Field) * index); |
286 | 0 | uprv_memcpy2(newFields + newZero + index + count, |
287 | 0 | oldFields + oldZero + index, |
288 | 0 | sizeof(Field) * (fLength - index)); |
289 | 0 |
|
290 | 0 | if (fUsingHeap) { |
291 | 0 | uprv_free(oldChars); |
292 | 0 | uprv_free(oldFields); |
293 | 0 | } |
294 | 0 | fUsingHeap = true; |
295 | 0 | fChars.heap.ptr = newChars; |
296 | 0 | fChars.heap.capacity = newCapacity; |
297 | 0 | fFields.heap.ptr = newFields; |
298 | 0 | fFields.heap.capacity = newCapacity; |
299 | 0 | fZero = newZero; |
300 | 0 | fLength += count; |
301 | 0 | } else { |
302 | 0 | int32_t newZero = oldCapacity / 2 - (fLength + count) / 2; |
303 | 0 |
|
304 | 0 | // C++ note: memmove is required because src and dest may overlap. |
305 | 0 | // First copy the entire string to the location of the prefix, and then move the suffix |
306 | 0 | // to make room for the new chars that the caller wants to insert. |
307 | 0 | uprv_memmove2(oldChars + newZero, oldChars + oldZero, sizeof(char16_t) * fLength); |
308 | 0 | uprv_memmove2(oldChars + newZero + index + count, |
309 | 0 | oldChars + newZero + index, |
310 | 0 | sizeof(char16_t) * (fLength - index)); |
311 | 0 | uprv_memmove2(oldFields + newZero, oldFields + oldZero, sizeof(Field) * fLength); |
312 | 0 | uprv_memmove2(oldFields + newZero + index + count, |
313 | 0 | oldFields + newZero + index, |
314 | 0 | sizeof(Field) * (fLength - index)); |
315 | 0 |
|
316 | 0 | fZero = newZero; |
317 | 0 | fLength += count; |
318 | 0 | } |
319 | 0 | return fZero + index; |
320 | 0 | } |
321 | | |
322 | 0 | int32_t NumberStringBuilder::remove(int32_t index, int32_t count) { |
323 | 0 | // TODO: Reset the heap here? (If the string after removal can fit on stack?) |
324 | 0 | int32_t position = index + fZero; |
325 | 0 | uprv_memmove2(getCharPtr() + position, |
326 | 0 | getCharPtr() + position + count, |
327 | 0 | sizeof(char16_t) * (fLength - index - count)); |
328 | 0 | uprv_memmove2(getFieldPtr() + position, |
329 | 0 | getFieldPtr() + position + count, |
330 | 0 | sizeof(Field) * (fLength - index - count)); |
331 | 0 | fLength -= count; |
332 | 0 | return position; |
333 | 0 | } |
334 | | |
335 | 0 | UnicodeString NumberStringBuilder::toUnicodeString() const { |
336 | 0 | return UnicodeString(getCharPtr() + fZero, fLength); |
337 | 0 | } |
338 | | |
339 | 0 | const UnicodeString NumberStringBuilder::toTempUnicodeString() const { |
340 | 0 | // Readonly-alias constructor: |
341 | 0 | return UnicodeString(FALSE, getCharPtr() + fZero, fLength); |
342 | 0 | } |
343 | | |
344 | 0 | UnicodeString NumberStringBuilder::toDebugString() const { |
345 | 0 | UnicodeString sb; |
346 | 0 | sb.append(u"<NumberStringBuilder [", -1); |
347 | 0 | sb.append(toUnicodeString()); |
348 | 0 | sb.append(u"] [", -1); |
349 | 0 | for (int i = 0; i < fLength; i++) { |
350 | 0 | if (fieldAt(i) == UNUM_FIELD_COUNT) { |
351 | 0 | sb.append(u'n'); |
352 | 0 | } else { |
353 | 0 | char16_t c; |
354 | 0 | switch (fieldAt(i)) { |
355 | 0 | case UNUM_SIGN_FIELD: |
356 | 0 | c = u'-'; |
357 | 0 | break; |
358 | 0 | case UNUM_INTEGER_FIELD: |
359 | 0 | c = u'i'; |
360 | 0 | break; |
361 | 0 | case UNUM_FRACTION_FIELD: |
362 | 0 | c = u'f'; |
363 | 0 | break; |
364 | 0 | case UNUM_EXPONENT_FIELD: |
365 | 0 | c = u'e'; |
366 | 0 | break; |
367 | 0 | case UNUM_EXPONENT_SIGN_FIELD: |
368 | 0 | c = u'+'; |
369 | 0 | break; |
370 | 0 | case UNUM_EXPONENT_SYMBOL_FIELD: |
371 | 0 | c = u'E'; |
372 | 0 | break; |
373 | 0 | case UNUM_DECIMAL_SEPARATOR_FIELD: |
374 | 0 | c = u'.'; |
375 | 0 | break; |
376 | 0 | case UNUM_GROUPING_SEPARATOR_FIELD: |
377 | 0 | c = u','; |
378 | 0 | break; |
379 | 0 | case UNUM_PERCENT_FIELD: |
380 | 0 | c = u'%'; |
381 | 0 | break; |
382 | 0 | case UNUM_PERMILL_FIELD: |
383 | 0 | c = u'‰'; |
384 | 0 | break; |
385 | 0 | case UNUM_CURRENCY_FIELD: |
386 | 0 | c = u'$'; |
387 | 0 | break; |
388 | 0 | default: |
389 | 0 | c = u'?'; |
390 | 0 | break; |
391 | 0 | } |
392 | 0 | sb.append(c); |
393 | 0 | } |
394 | 0 | } |
395 | 0 | sb.append(u"]>", -1); |
396 | 0 | return sb; |
397 | 0 | } |
398 | | |
399 | 0 | const char16_t *NumberStringBuilder::chars() const { |
400 | 0 | return getCharPtr() + fZero; |
401 | 0 | } |
402 | | |
403 | 0 | bool NumberStringBuilder::contentEquals(const NumberStringBuilder &other) const { |
404 | 0 | if (fLength != other.fLength) { |
405 | 0 | return false; |
406 | 0 | } |
407 | 0 | for (int32_t i = 0; i < fLength; i++) { |
408 | 0 | if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) { |
409 | 0 | return false; |
410 | 0 | } |
411 | 0 | } |
412 | 0 | return true; |
413 | 0 | } |
414 | | |
415 | 0 | bool NumberStringBuilder::nextFieldPosition(FieldPosition& fp, UErrorCode& status) const { |
416 | 0 | int32_t rawField = fp.getField(); |
417 | 0 |
|
418 | 0 | if (rawField == FieldPosition::DONT_CARE) { |
419 | 0 | return FALSE; |
420 | 0 | } |
421 | 0 |
|
422 | 0 | if (rawField < 0 || rawField >= UNUM_FIELD_COUNT) { |
423 | 0 | status = U_ILLEGAL_ARGUMENT_ERROR; |
424 | 0 | return FALSE; |
425 | 0 | } |
426 | 0 |
|
427 | 0 | auto field = static_cast<Field>(rawField); |
428 | 0 |
|
429 | 0 | bool seenStart = false; |
430 | 0 | int32_t fractionStart = -1; |
431 | 0 | int32_t startIndex = fp.getEndIndex(); |
432 | 0 | for (int i = fZero + startIndex; i <= fZero + fLength; i++) { |
433 | 0 | Field _field = UNUM_FIELD_COUNT; |
434 | 0 | if (i < fZero + fLength) { |
435 | 0 | _field = getFieldPtr()[i]; |
436 | 0 | } |
437 | 0 | if (seenStart && field != _field) { |
438 | 0 | // Special case: GROUPING_SEPARATOR counts as an INTEGER. |
439 | 0 | if (field == UNUM_INTEGER_FIELD && _field == UNUM_GROUPING_SEPARATOR_FIELD) { |
440 | 0 | continue; |
441 | 0 | } |
442 | 0 | fp.setEndIndex(i - fZero); |
443 | 0 | break; |
444 | 0 | } else if (!seenStart && field == _field) { |
445 | 0 | fp.setBeginIndex(i - fZero); |
446 | 0 | seenStart = true; |
447 | 0 | } |
448 | 0 | if (_field == UNUM_INTEGER_FIELD || _field == UNUM_DECIMAL_SEPARATOR_FIELD) { |
449 | 0 | fractionStart = i - fZero + 1; |
450 | 0 | } |
451 | 0 | } |
452 | 0 |
|
453 | 0 | // Backwards compatibility: FRACTION needs to start after INTEGER if empty. |
454 | 0 | // Do not return that a field was found, though, since there is not actually a fraction part. |
455 | 0 | if (field == UNUM_FRACTION_FIELD && !seenStart && fractionStart != -1) { |
456 | 0 | fp.setBeginIndex(fractionStart); |
457 | 0 | fp.setEndIndex(fractionStart); |
458 | 0 | } |
459 | 0 |
|
460 | 0 | return seenStart; |
461 | 0 | } |
462 | | |
463 | | void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpih, |
464 | 0 | UErrorCode& status) const { |
465 | 0 | Field current = UNUM_FIELD_COUNT; |
466 | 0 | int32_t currentStart = -1; |
467 | 0 | for (int32_t i = 0; i < fLength; i++) { |
468 | 0 | Field field = fieldAt(i); |
469 | 0 | if (current == UNUM_INTEGER_FIELD && field == UNUM_GROUPING_SEPARATOR_FIELD) { |
470 | 0 | // Special case: GROUPING_SEPARATOR counts as an INTEGER. |
471 | 0 | fpih.addAttribute(UNUM_GROUPING_SEPARATOR_FIELD, i, i + 1); |
472 | 0 | } else if (current != field) { |
473 | 0 | if (current != UNUM_FIELD_COUNT) { |
474 | 0 | fpih.addAttribute(current, currentStart, i); |
475 | 0 | } |
476 | 0 | current = field; |
477 | 0 | currentStart = i; |
478 | 0 | } |
479 | 0 | if (U_FAILURE(status)) { |
480 | 0 | return; |
481 | 0 | } |
482 | 0 | } |
483 | 0 | if (current != UNUM_FIELD_COUNT) { |
484 | 0 | fpih.addAttribute(current, currentStart, fLength); |
485 | 0 | } |
486 | 0 | } |
487 | | |
488 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |