1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
6 * Copyright (C) 2013-2016, International Business Machines
7 * Corporation and others. All Rights Reserved.
9 *******************************************************************************
10 * file name: listformatter.cpp
12 * tab size: 8 (not used)
15 * created on: 2012aug27
16 * created by: Umesh P. Nair
20 #include "unicode/fpositer.h" // FieldPositionIterator
21 #include "unicode/listformatter.h"
22 #include "unicode/simpleformatter.h"
23 #include "unicode/ulistformatter.h"
34 #include "formattedval_impl.h"
38 struct ListFormatInternal
: public UMemory
{
39 SimpleFormatter twoPattern
;
40 SimpleFormatter startPattern
;
41 SimpleFormatter middlePattern
;
42 SimpleFormatter endPattern
;
45 const UnicodeString
& two
,
46 const UnicodeString
& start
,
47 const UnicodeString
& middle
,
48 const UnicodeString
& end
,
49 UErrorCode
&errorCode
) :
50 twoPattern(two
, 2, 2, errorCode
),
51 startPattern(start
, 2, 2, errorCode
),
52 middlePattern(middle
, 2, 2, errorCode
),
53 endPattern(end
, 2, 2, errorCode
) {}
55 ListFormatInternal(const ListFormatData
&data
, UErrorCode
&errorCode
) :
56 twoPattern(data
.twoPattern
, errorCode
),
57 startPattern(data
.startPattern
, errorCode
),
58 middlePattern(data
.middlePattern
, errorCode
),
59 endPattern(data
.endPattern
, errorCode
) { }
61 ListFormatInternal(const ListFormatInternal
&other
) :
62 twoPattern(other
.twoPattern
),
63 startPattern(other
.startPattern
),
64 middlePattern(other
.middlePattern
),
65 endPattern(other
.endPattern
) { }
69 #if !UCONFIG_NO_FORMATTING
70 class FormattedListData
: public FormattedValueFieldPositionIteratorImpl
{
72 FormattedListData(UErrorCode
& status
) : FormattedValueFieldPositionIteratorImpl(5, status
) {}
73 virtual ~FormattedListData();
76 FormattedListData::~FormattedListData() = default;
78 UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedList
)
82 static Hashtable
* listPatternHash
= nullptr;
83 static const char STANDARD_STYLE
[] = "standard";
86 static UBool U_CALLCONV
uprv_listformatter_cleanup() {
87 delete listPatternHash
;
88 listPatternHash
= nullptr;
92 static void U_CALLCONV
93 uprv_deleteListFormatInternal(void *obj
) {
94 delete static_cast<ListFormatInternal
*>(obj
);
99 ListFormatter::ListFormatter(const ListFormatter
& other
) :
100 owned(other
.owned
), data(other
.data
) {
101 if (other
.owned
!= nullptr) {
102 owned
= new ListFormatInternal(*other
.owned
);
107 ListFormatter
& ListFormatter::operator=(const ListFormatter
& other
) {
108 if (this == &other
) {
113 owned
= new ListFormatInternal(*other
.owned
);
122 void ListFormatter::initializeHash(UErrorCode
& errorCode
) {
123 if (U_FAILURE(errorCode
)) {
127 listPatternHash
= new Hashtable();
128 if (listPatternHash
== nullptr) {
129 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
133 listPatternHash
->setValueDeleter(uprv_deleteListFormatInternal
);
134 ucln_i18n_registerCleanup(UCLN_I18N_LIST_FORMATTER
, uprv_listformatter_cleanup
);
138 const ListFormatInternal
* ListFormatter::getListFormatInternal(
139 const Locale
& locale
, const char *style
, UErrorCode
& errorCode
) {
140 if (U_FAILURE(errorCode
)) {
143 CharString
keyBuffer(locale
.getName(), errorCode
);
144 keyBuffer
.append(':', errorCode
).append(style
, errorCode
);
145 UnicodeString
key(keyBuffer
.data(), -1, US_INV
);
146 ListFormatInternal
* result
= nullptr;
147 static UMutex
*listFormatterMutex
= STATIC_NEW(UMutex
);
149 Mutex
m(listFormatterMutex
);
150 if (listPatternHash
== nullptr) {
151 initializeHash(errorCode
);
152 if (U_FAILURE(errorCode
)) {
156 result
= static_cast<ListFormatInternal
*>(listPatternHash
->get(key
));
158 if (result
!= nullptr) {
161 result
= loadListFormatInternal(locale
, style
, errorCode
);
162 if (U_FAILURE(errorCode
)) {
167 Mutex
m(listFormatterMutex
);
168 ListFormatInternal
* temp
= static_cast<ListFormatInternal
*>(listPatternHash
->get(key
));
169 if (temp
!= nullptr) {
173 listPatternHash
->put(key
, result
, errorCode
);
174 if (U_FAILURE(errorCode
)) {
182 static const UChar solidus
= 0x2F;
183 static const UChar aliasPrefix
[] = { 0x6C,0x69,0x73,0x74,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x2F }; // "listPattern/"
185 kAliasPrefixLen
= UPRV_LENGTHOF(aliasPrefix
),
186 kStyleLenMax
= 24 // longest currently is 14
189 struct ListFormatter::ListPatternsSink
: public ResourceSink
{
190 UnicodeString two
, start
, middle
, end
;
191 #if ((U_PLATFORM == U_PF_AIX) || (U_PLATFORM == U_PF_OS390)) && (U_CPLUSPLUS_VERSION < 11)
192 char aliasedStyle
[kStyleLenMax
+1];
194 uprv_memset(aliasedStyle
, 0, kStyleLenMax
+1);
197 char aliasedStyle
[kStyleLenMax
+1] = {0};
199 ListPatternsSink() {}
201 virtual ~ListPatternsSink();
203 void setAliasedStyle(UnicodeString alias
) {
204 int32_t startIndex
= alias
.indexOf(aliasPrefix
, kAliasPrefixLen
, 0);
205 if (startIndex
< 0) {
208 startIndex
+= kAliasPrefixLen
;
209 int32_t endIndex
= alias
.indexOf(solidus
, startIndex
);
211 endIndex
= alias
.length();
213 alias
.extract(startIndex
, endIndex
-startIndex
, aliasedStyle
, kStyleLenMax
+1, US_INV
);
214 aliasedStyle
[kStyleLenMax
] = 0;
217 void handleValueForPattern(ResourceValue
&value
, UnicodeString
&pattern
, UErrorCode
&errorCode
) {
218 if (pattern
.isEmpty()) {
219 if (value
.getType() == URES_ALIAS
) {
220 if (aliasedStyle
[0] == 0) {
221 setAliasedStyle(value
.getAliasUnicodeString(errorCode
));
224 pattern
= value
.getUnicodeString(errorCode
);
229 virtual void put(const char *key
, ResourceValue
&value
, UBool
/*noFallback*/,
230 UErrorCode
&errorCode
) {
232 if (value
.getType() == URES_ALIAS
) {
233 setAliasedStyle(value
.getAliasUnicodeString(errorCode
));
236 ResourceTable listPatterns
= value
.getTable(errorCode
);
237 for (int i
= 0; U_SUCCESS(errorCode
) && listPatterns
.getKeyAndValue(i
, key
, value
); ++i
) {
238 if (uprv_strcmp(key
, "2") == 0) {
239 handleValueForPattern(value
, two
, errorCode
);
240 } else if (uprv_strcmp(key
, "end") == 0) {
241 handleValueForPattern(value
, end
, errorCode
);
242 } else if (uprv_strcmp(key
, "middle") == 0) {
243 handleValueForPattern(value
, middle
, errorCode
);
244 } else if (uprv_strcmp(key
, "start") == 0) {
245 handleValueForPattern(value
, start
, errorCode
);
251 // Virtual destructors must be defined out of line.
252 ListFormatter::ListPatternsSink::~ListPatternsSink() {}
254 ListFormatInternal
* ListFormatter::loadListFormatInternal(
255 const Locale
& locale
, const char * style
, UErrorCode
& errorCode
) {
256 UResourceBundle
* rb
= ures_open(nullptr, locale
.getName(), &errorCode
);
257 rb
= ures_getByKeyWithFallback(rb
, "listPattern", rb
, &errorCode
);
258 if (U_FAILURE(errorCode
)) {
262 ListFormatter::ListPatternsSink sink
;
263 char currentStyle
[kStyleLenMax
+1];
264 uprv_strncpy(currentStyle
, style
, kStyleLenMax
);
265 currentStyle
[kStyleLenMax
] = 0;
268 ures_getAllItemsWithFallback(rb
, currentStyle
, sink
, errorCode
);
269 if (U_FAILURE(errorCode
) || sink
.aliasedStyle
[0] == 0 || uprv_strcmp(currentStyle
, sink
.aliasedStyle
) == 0) {
272 uprv_strcpy(currentStyle
, sink
.aliasedStyle
);
275 if (U_FAILURE(errorCode
)) {
278 if (sink
.two
.isEmpty() || sink
.start
.isEmpty() || sink
.middle
.isEmpty() || sink
.end
.isEmpty()) {
279 errorCode
= U_MISSING_RESOURCE_ERROR
;
282 ListFormatInternal
* result
= new ListFormatInternal(sink
.two
, sink
.start
, sink
.middle
, sink
.end
, errorCode
);
283 if (result
== nullptr) {
284 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
287 if (U_FAILURE(errorCode
)) {
294 ListFormatter
* ListFormatter::createInstance(UErrorCode
& errorCode
) {
295 Locale locale
; // The default locale.
296 return createInstance(locale
, errorCode
);
299 ListFormatter
* ListFormatter::createInstance(const Locale
& locale
, UErrorCode
& errorCode
) {
300 return createInstance(locale
, STANDARD_STYLE
, errorCode
);
303 ListFormatter
* ListFormatter::createInstance(const Locale
& locale
, const char *style
, UErrorCode
& errorCode
) {
304 const ListFormatInternal
* listFormatInternal
= getListFormatInternal(locale
, style
, errorCode
);
305 if (U_FAILURE(errorCode
)) {
308 ListFormatter
* p
= new ListFormatter(listFormatInternal
);
310 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
316 ListFormatter::ListFormatter(const ListFormatData
& listFormatData
, UErrorCode
&errorCode
) {
317 owned
= new ListFormatInternal(listFormatData
, errorCode
);
321 ListFormatter::ListFormatter(const ListFormatInternal
* listFormatterInternal
) : owned(nullptr), data(listFormatterInternal
) {
324 ListFormatter::~ListFormatter() {
329 * Joins first and second using the pattern pat.
330 * On entry offset is an offset into first or -1 if offset unspecified.
331 * On exit offset is offset of second in result if recordOffset was set
332 * Otherwise if it was >=0 it is set to point into result where it used
333 * to point into first. On exit, result is the join of first and second
334 * according to pat. Any previous value of result gets replaced.
336 static void joinStringsAndReplace(
337 const SimpleFormatter
& pat
,
338 const UnicodeString
& first
,
339 const UnicodeString
& second
,
340 UnicodeString
&result
,
343 int32_t *offsetFirst
,
344 int32_t *offsetSecond
,
345 UErrorCode
& errorCode
) {
346 if (U_FAILURE(errorCode
)) {
349 const UnicodeString
*params
[2] = {&first
, &second
};
351 pat
.formatAndReplace(
353 UPRV_LENGTHOF(params
),
356 UPRV_LENGTHOF(offsets
),
358 if (U_FAILURE(errorCode
)) {
361 if (offsets
[0] == -1 || offsets
[1] == -1) {
362 errorCode
= U_INVALID_FORMAT_ERROR
;
367 } else if (offset
>= 0) {
368 offset
+= offsets
[0];
370 if (offsetFirst
!= nullptr) *offsetFirst
= offsets
[0];
371 if (offsetSecond
!= nullptr) *offsetSecond
= offsets
[1];
374 UnicodeString
& ListFormatter::format(
375 const UnicodeString items
[],
377 UnicodeString
& appendTo
,
378 UErrorCode
& errorCode
) const {
380 return format(items
, nItems
, appendTo
, -1, offset
, errorCode
);
383 #if !UCONFIG_NO_FORMATTING
384 UnicodeString
& ListFormatter::format(
385 const UnicodeString items
[],
387 UnicodeString
& appendTo
,
388 FieldPositionIterator
* posIter
,
389 UErrorCode
& errorCode
) const {
391 FieldPositionIteratorHandler
handler(posIter
, errorCode
);
392 return format_(items
, nItems
, appendTo
, -1, offset
, &handler
, errorCode
);
396 UnicodeString
& ListFormatter::format(
397 const UnicodeString items
[],
399 UnicodeString
& appendTo
,
402 UErrorCode
& errorCode
) const {
403 return format_(items
, nItems
, appendTo
, index
, offset
, nullptr, errorCode
);
406 #if !UCONFIG_NO_FORMATTING
407 FormattedList
ListFormatter::formatStringsToValue(
408 const UnicodeString items
[],
410 UErrorCode
& errorCode
) const {
411 LocalPointer
<FormattedListData
> result(new FormattedListData(errorCode
), errorCode
);
412 if (U_FAILURE(errorCode
)) {
413 return FormattedList(errorCode
);
415 UnicodeString string
;
417 auto handler
= result
->getHandler(errorCode
);
418 handler
.setCategory(UFIELD_CATEGORY_LIST
);
419 format_(items
, nItems
, string
, -1, offset
, &handler
, errorCode
);
420 handler
.getError(errorCode
);
421 result
->appendString(string
, errorCode
);
422 if (U_FAILURE(errorCode
)) {
423 return FormattedList(errorCode
);
426 // Add span fields and sort
427 ConstrainedFieldPosition cfpos
;
428 cfpos
.constrainField(UFIELD_CATEGORY_LIST
, ULISTFMT_ELEMENT_FIELD
);
430 handler
.setCategory(UFIELD_CATEGORY_LIST_SPAN
);
431 while (result
->nextPosition(cfpos
, errorCode
)) {
432 handler
.addAttribute(i
++, cfpos
.getStart(), cfpos
.getLimit());
434 handler
.getError(errorCode
);
435 if (U_FAILURE(errorCode
)) {
436 return FormattedList(errorCode
);
440 return FormattedList(result
.orphan());
444 UnicodeString
& ListFormatter::format_(
445 const UnicodeString items
[],
447 UnicodeString
& appendTo
,
450 FieldPositionHandler
* handler
,
451 UErrorCode
& errorCode
) const {
452 #if !UCONFIG_NO_FORMATTING
454 if (U_FAILURE(errorCode
)) {
457 if (data
== nullptr) {
458 errorCode
= U_INVALID_STATE_ERROR
;
467 offset
= appendTo
.length();
469 if (handler
!= nullptr) {
470 handler
->addAttribute(ULISTFMT_ELEMENT_FIELD
,
472 appendTo
.length() + items
[0].length());
474 appendTo
.append(items
[0]);
477 UnicodeString
result(items
[0]);
482 int32_t offsetSecond
;
483 int32_t prefixLength
= 0;
484 // for n items, there are 2 * (n + 1) boundary including 0 and the upper
486 MaybeStackArray
<int32_t, 10> offsets((handler
!= nullptr) ? 2 * (nItems
+ 1): 0);
487 joinStringsAndReplace(
488 nItems
== 2 ? data
->twoPattern
: data
->startPattern
,
497 if (handler
!= nullptr) {
499 prefixLength
+= offsetFirst
;
500 offsets
[1] = offsetSecond
- prefixLength
;
503 for (int32_t i
= 2; i
< nItems
- 1; ++i
) {
504 joinStringsAndReplace(
514 if (handler
!= nullptr) {
515 prefixLength
+= offsetFirst
;
516 offsets
[i
] = offsetSecond
- prefixLength
;
519 joinStringsAndReplace(
529 if (handler
!= nullptr) {
530 prefixLength
+= offsetFirst
;
531 offsets
[nItems
- 1] = offsetSecond
- prefixLength
;
534 if (handler
!= nullptr) {
535 // If there are already some data in appendTo, we need to adjust the index
536 // by shifting that lenght while insert into handler.
537 int32_t shift
= appendTo
.length() + prefixLength
;
538 // Output the ULISTFMT_ELEMENT_FIELD in the order of the input elements
539 for (int32_t i
= 0; i
< nItems
; ++i
) {
540 offsets
[i
+ nItems
] = offsets
[i
] + items
[i
].length() + shift
;
542 handler
->addAttribute(
543 ULISTFMT_ELEMENT_FIELD
, // id
545 offsets
[i
+ nItems
]); // limit
547 // The locale pattern may reorder the items (such as in ur-IN locale),
548 // so we cannot assume the array is in accendning order.
549 // To handle the edging case, just insert the two ends into the array
550 // and sort. Then we output ULISTFMT_LITERAL_FIELD if the indecies
551 // between the even and odd position are not the same in the sorted array.
552 offsets
[2 * nItems
] = shift
- prefixLength
;
553 offsets
[2 * nItems
+ 1] = result
.length() + shift
- prefixLength
;
554 uprv_sortArray(offsets
.getAlias(), 2 * (nItems
+ 1), sizeof(int32_t),
555 uprv_int32Comparator
, nullptr,
557 for (int32_t i
= 0; i
<= nItems
; ++i
) {
558 if (offsets
[i
* 2] != offsets
[i
* 2 + 1]) {
559 handler
->addAttribute(
560 ULISTFMT_LITERAL_FIELD
, // id
561 offsets
[i
* 2], // index
562 offsets
[i
* 2 + 1]); // limit
566 if (U_SUCCESS(errorCode
)) {
568 offset
+= appendTo
.length();