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"
24 #include "unicode/uscript.h"
35 #include "formattedval_impl.h"
41 class PatternHandler
: public UObject
{
43 PatternHandler(const UnicodeString
& two
, const UnicodeString
& end
, UErrorCode
& errorCode
) :
44 twoPattern(two
, 2, 2, errorCode
),
45 endPattern(end
, 2, 2, errorCode
) { }
47 PatternHandler(const SimpleFormatter
& two
, const SimpleFormatter
& end
) :
51 virtual ~PatternHandler();
53 virtual PatternHandler
* clone() const { return new PatternHandler(twoPattern
, endPattern
); }
55 virtual const SimpleFormatter
& getTwoPattern(const UnicodeString
&, const UnicodeString
&) const {
59 virtual const SimpleFormatter
& getEndPattern(const UnicodeString
&) const {
64 SimpleFormatter twoPattern
;
65 SimpleFormatter endPattern
;
68 PatternHandler::~PatternHandler() {
71 class ContextualHandler
: public PatternHandler
{
73 ContextualHandler(bool (*testFunc
)(const UnicodeString
& text
),
74 const UnicodeString
& thenTwo
,
75 const UnicodeString
& elseTwo
,
76 const UnicodeString
& thenEnd
,
77 const UnicodeString
& elseEnd
,
78 UErrorCode
& errorCode
) :
79 PatternHandler(elseTwo
, elseEnd
, errorCode
),
81 thenTwoPattern(thenTwo
, 2, 2, errorCode
),
82 thenEndPattern(thenEnd
, 2, 2, errorCode
) { }
84 ContextualHandler(bool (*testFunc
)(const UnicodeString
& text
),
85 const SimpleFormatter
& thenTwo
, SimpleFormatter elseTwo
,
86 const SimpleFormatter
& thenEnd
, SimpleFormatter elseEnd
) :
87 PatternHandler(elseTwo
, elseEnd
),
89 thenTwoPattern(thenTwo
),
90 thenEndPattern(thenEnd
) { }
92 ~ContextualHandler() override
;
94 PatternHandler
* clone() const override
{
95 return new ContextualHandler(
96 test
, thenTwoPattern
, twoPattern
, thenEndPattern
, endPattern
);
99 const SimpleFormatter
& getTwoPattern(
100 const UnicodeString
&, /*ignored*/
101 const UnicodeString
& text
) const override
{
102 return (test
)(text
) ? thenTwoPattern
: twoPattern
;
105 const SimpleFormatter
& getEndPattern(
106 const UnicodeString
& text
) const override
{
107 return (test
)(text
) ? thenEndPattern
: endPattern
;
111 bool (*test
)(const UnicodeString
&);
112 SimpleFormatter thenTwoPattern
;
113 SimpleFormatter thenEndPattern
;
116 ContextualHandler::~ContextualHandler() {
119 class ThaiHandler
: public PatternHandler
{
121 ThaiHandler(const UnicodeString
& two
, const UnicodeString
& end
, UErrorCode
& errorCode
) :
122 PatternHandler(two
, end
, errorCode
),
127 spaceTwoSpacePattern(),
129 bool needToDeleteSpaceAfter0
= false;
130 UnicodeString tempPattern
= two
;
131 if (tempPattern
.indexOf(UnicodeString(u
"{0} ")) < 0) {
132 tempPattern
.findAndReplace(UnicodeString(u
"{0}"), UnicodeString(u
"{0} "));
133 needToDeleteSpaceAfter0
= true;
135 spaceTwoPattern
= SimpleFormatter(tempPattern
, 2, 2, errorCode
);
136 if (tempPattern
.indexOf(UnicodeString(u
" {1}")) < 0) {
137 tempPattern
.findAndReplace(UnicodeString(u
"{1}"), UnicodeString(u
" {1}"));
139 spaceTwoSpacePattern
= SimpleFormatter(tempPattern
, 2, 2, errorCode
);
140 if (needToDeleteSpaceAfter0
) {
141 tempPattern
.findAndReplace(UnicodeString(u
"{0} "), UnicodeString(u
"{0}"));
143 twoSpacePattern
= SimpleFormatter(tempPattern
, 2, 2, errorCode
);
146 if (tempPattern
.indexOf(UnicodeString(u
" {1}")) < 0) {
147 tempPattern
.findAndReplace(UnicodeString(u
"{1}"), UnicodeString(u
" {1}"));
149 spaceEndPattern
= SimpleFormatter(tempPattern
, 2, 2, errorCode
);
152 ~ThaiHandler() override
;
154 PatternHandler
* clone() const override
{
155 UErrorCode dummyErr
= U_ZERO_ERROR
;
156 return new ThaiHandler(twoPatternText
, endPatternText
, dummyErr
);
159 const SimpleFormatter
& getTwoPattern(
160 const UnicodeString
& textBefore
,
161 const UnicodeString
& textAfter
) const override
{
162 UErrorCode err
= U_ZERO_ERROR
;
163 bool insertSpaceBefore
= !textBefore
.isEmpty() && uscript_getScript(textBefore
[textBefore
.length() - 1], &err
) != USCRIPT_THAI
;
164 bool insertSpaceAfter
= !textAfter
.isEmpty() && uscript_getScript(textAfter
[0], &err
) != USCRIPT_THAI
;
166 if (insertSpaceBefore
) {
167 return insertSpaceAfter
? spaceTwoSpacePattern
: spaceTwoPattern
;
169 return insertSpaceAfter
? twoSpacePattern
: twoPattern
;
173 const SimpleFormatter
& getEndPattern(
174 const UnicodeString
& text
) const override
{
175 UErrorCode err
= U_ZERO_ERROR
;
176 if (!text
.isEmpty() && uscript_getScript(text
[0], &err
) != USCRIPT_THAI
) {
177 return spaceEndPattern
;
184 UnicodeString twoPatternText
;
185 UnicodeString endPatternText
;
186 SimpleFormatter spaceTwoPattern
;
187 SimpleFormatter twoSpacePattern
;
188 SimpleFormatter spaceTwoSpacePattern
;
189 SimpleFormatter spaceEndPattern
;
192 ThaiHandler::~ThaiHandler() {
195 static const char16_t *spanishY
= u
"{0} y {1}";
196 static const char16_t *spanishE
= u
"{0} e {1}";
197 static const char16_t *spanishO
= u
"{0} o {1}";
198 static const char16_t *spanishU
= u
"{0} u {1}";
199 static const char16_t *hebrewVav
= u
"{0} \u05D5{1}";
200 static const char16_t *hebrewVavDash
= u
"{0} \u05D5-{1}";
202 // Condiction to change to e.
203 // Starts with "hi" or "i" but not with "hie" nor "hia"
204 static bool shouldChangeToE(const UnicodeString
& text
) {
205 int32_t len
= text
.length();
206 if (len
== 0) { return false; }
207 // Case insensitive match hi but not hie nor hia.
208 if ((text
[0] == u
'h' || text
[0] == u
'H') &&
209 ((len
> 1) && (text
[1] == u
'i' || text
[1] == u
'I')) &&
210 ((len
== 2) || !(text
[2] == u
'a' || text
[2] == u
'A' || text
[2] == u
'e' || text
[2] == u
'E'))) {
213 // Case insensitive for "start with i"
214 if (text
[0] == u
'i' || text
[0] == u
'I') { return true; }
218 // Condiction to change to u.
219 // Starts with "o", "ho", and "8". Also "11" by itself.
220 // re: ^((o|ho|8).*|11)$
221 static bool shouldChangeToU(const UnicodeString
& text
) {
222 int32_t len
= text
.length();
223 if (len
== 0) { return false; }
224 // Case insensitive match o.* and 8.*
225 if (text
[0] == u
'o' || text
[0] == u
'O' || text
[0] == u
'8') { return true; }
226 // Case insensitive match ho.*
227 if ((text
[0] == u
'h' || text
[0] == u
'H') &&
228 ((len
> 1) && (text
[1] == 'o' || text
[1] == u
'O'))) {
231 // match "^11$" and "^11 .*"
232 if ((len
>= 2) && text
[0] == u
'1' && text
[1] == u
'1' && (len
== 2 || text
[2] == u
' ')) { return true; }
236 // Condiction to change to VAV follow by a dash.
237 // Starts with non Hebrew letter.
238 static bool shouldChangeToVavDash(const UnicodeString
& text
) {
239 if (text
.isEmpty()) { return false; }
240 UErrorCode status
= U_ZERO_ERROR
;
241 return uscript_getScript(text
.char32At(0), &status
) != USCRIPT_HEBREW
;
244 PatternHandler
* createPatternHandler(
245 const char* lang
, const UnicodeString
& two
, const UnicodeString
& end
,
246 UErrorCode
& status
) {
247 if (uprv_strcmp(lang
, "es") == 0) {
249 UnicodeString
spanishYStr(TRUE
, spanishY
, -1);
250 bool twoIsY
= two
== spanishYStr
;
251 bool endIsY
= end
== spanishYStr
;
252 if (twoIsY
|| endIsY
) {
253 UnicodeString
replacement(TRUE
, spanishE
, -1);
254 return new ContextualHandler(
256 twoIsY
? replacement
: two
, two
,
257 endIsY
? replacement
: end
, end
, status
);
259 UnicodeString
spanishOStr(TRUE
, spanishO
, -1);
260 bool twoIsO
= two
== spanishOStr
;
261 bool endIsO
= end
== spanishOStr
;
262 if (twoIsO
|| endIsO
) {
263 UnicodeString
replacement(TRUE
, spanishU
, -1);
264 return new ContextualHandler(
266 twoIsO
? replacement
: two
, two
,
267 endIsO
? replacement
: end
, end
, status
);
269 } else if (uprv_strcmp(lang
, "he") == 0 || uprv_strcmp(lang
, "iw") == 0) {
271 UnicodeString
hebrewVavStr(TRUE
, hebrewVav
, -1);
272 bool twoIsVav
= two
== hebrewVavStr
;
273 bool endIsVav
= end
== hebrewVavStr
;
274 if (twoIsVav
|| endIsVav
) {
275 UnicodeString
replacement(TRUE
, hebrewVavDash
, -1);
276 return new ContextualHandler(
277 shouldChangeToVavDash
,
278 twoIsVav
? replacement
: two
, two
,
279 endIsVav
? replacement
: end
, end
, status
);
281 } else if (uprv_strcmp(lang
, "th") == 0) {
282 return new ThaiHandler(two
, end
, status
);
284 return new PatternHandler(two
, end
, status
);
289 struct ListFormatInternal
: public UMemory
{
290 SimpleFormatter startPattern
;
291 SimpleFormatter middlePattern
;
292 LocalPointer
<PatternHandler
> patternHandler
;
295 const UnicodeString
& two
,
296 const UnicodeString
& start
,
297 const UnicodeString
& middle
,
298 const UnicodeString
& end
,
299 const Locale
& locale
,
300 UErrorCode
&errorCode
) :
301 startPattern(start
, 2, 2, errorCode
),
302 middlePattern(middle
, 2, 2, errorCode
),
303 patternHandler(createPatternHandler(locale
.getLanguage(), two
, end
, errorCode
), errorCode
) { }
305 ListFormatInternal(const ListFormatData
&data
, UErrorCode
&errorCode
) :
306 startPattern(data
.startPattern
, errorCode
),
307 middlePattern(data
.middlePattern
, errorCode
),
308 patternHandler(createPatternHandler(
309 data
.locale
.getLanguage(), data
.twoPattern
, data
.endPattern
, errorCode
), errorCode
) { }
311 ListFormatInternal(const ListFormatInternal
&other
) :
312 startPattern(other
.startPattern
),
313 middlePattern(other
.middlePattern
),
314 patternHandler(other
.patternHandler
->clone()) { }
318 #if !UCONFIG_NO_FORMATTING
319 class FormattedListData
: public FormattedValueFieldPositionIteratorImpl
{
321 FormattedListData(UErrorCode
& status
) : FormattedValueFieldPositionIteratorImpl(5, status
) {}
322 virtual ~FormattedListData();
325 FormattedListData::~FormattedListData() = default;
327 UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedList
)
331 static Hashtable
* listPatternHash
= nullptr;
334 static UBool U_CALLCONV
uprv_listformatter_cleanup() {
335 delete listPatternHash
;
336 listPatternHash
= nullptr;
340 static void U_CALLCONV
341 uprv_deleteListFormatInternal(void *obj
) {
342 delete static_cast<ListFormatInternal
*>(obj
);
347 ListFormatter::ListFormatter(const ListFormatter
& other
) :
348 owned(other
.owned
), data(other
.data
) {
349 if (other
.owned
!= nullptr) {
350 owned
= new ListFormatInternal(*other
.owned
);
355 ListFormatter
& ListFormatter::operator=(const ListFormatter
& other
) {
356 if (this == &other
) {
361 owned
= new ListFormatInternal(*other
.owned
);
370 void ListFormatter::initializeHash(UErrorCode
& errorCode
) {
371 if (U_FAILURE(errorCode
)) {
375 listPatternHash
= new Hashtable();
376 if (listPatternHash
== nullptr) {
377 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
381 listPatternHash
->setValueDeleter(uprv_deleteListFormatInternal
);
382 ucln_i18n_registerCleanup(UCLN_I18N_LIST_FORMATTER
, uprv_listformatter_cleanup
);
386 const ListFormatInternal
* ListFormatter::getListFormatInternal(
387 const Locale
& locale
, const char *style
, UErrorCode
& errorCode
) {
388 if (U_FAILURE(errorCode
)) {
391 CharString
keyBuffer(locale
.getName(), errorCode
);
392 keyBuffer
.append(':', errorCode
).append(style
, errorCode
);
393 UnicodeString
key(keyBuffer
.data(), -1, US_INV
);
394 ListFormatInternal
* result
= nullptr;
395 static UMutex listFormatterMutex
;
397 Mutex
m(&listFormatterMutex
);
398 if (listPatternHash
== nullptr) {
399 initializeHash(errorCode
);
400 if (U_FAILURE(errorCode
)) {
404 result
= static_cast<ListFormatInternal
*>(listPatternHash
->get(key
));
406 if (result
!= nullptr) {
409 result
= loadListFormatInternal(locale
, style
, errorCode
);
410 if (U_FAILURE(errorCode
)) {
415 Mutex
m(&listFormatterMutex
);
416 ListFormatInternal
* temp
= static_cast<ListFormatInternal
*>(listPatternHash
->get(key
));
417 if (temp
!= nullptr) {
421 listPatternHash
->put(key
, result
, errorCode
);
422 if (U_FAILURE(errorCode
)) {
430 #if !UCONFIG_NO_FORMATTING
431 static const char* typeWidthToStyleString(UListFormatterType type
, UListFormatterWidth width
) {
433 case ULISTFMT_TYPE_AND
:
435 case ULISTFMT_WIDTH_WIDE
:
437 case ULISTFMT_WIDTH_SHORT
:
438 return "standard-short";
439 case ULISTFMT_WIDTH_NARROW
:
440 return "standard-narrow";
446 case ULISTFMT_TYPE_OR
:
448 case ULISTFMT_WIDTH_WIDE
:
450 case ULISTFMT_WIDTH_SHORT
:
452 case ULISTFMT_WIDTH_NARROW
:
459 case ULISTFMT_TYPE_UNITS
:
461 case ULISTFMT_WIDTH_WIDE
:
463 case ULISTFMT_WIDTH_SHORT
:
465 case ULISTFMT_WIDTH_NARROW
:
466 return "unit-narrow";
476 static const UChar solidus
= 0x2F;
477 static const UChar aliasPrefix
[] = { 0x6C,0x69,0x73,0x74,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x2F }; // "listPattern/"
479 kAliasPrefixLen
= UPRV_LENGTHOF(aliasPrefix
),
480 kStyleLenMax
= 24 // longest currently is 14
483 struct ListFormatter::ListPatternsSink
: public ResourceSink
{
484 UnicodeString two
, start
, middle
, end
;
485 #if ((U_PLATFORM == U_PF_AIX) || (U_PLATFORM == U_PF_OS390)) && (U_CPLUSPLUS_VERSION < 11)
486 char aliasedStyle
[kStyleLenMax
+1];
488 uprv_memset(aliasedStyle
, 0, kStyleLenMax
+1);
491 char aliasedStyle
[kStyleLenMax
+1] = {0};
493 ListPatternsSink() {}
495 virtual ~ListPatternsSink();
497 void setAliasedStyle(UnicodeString alias
) {
498 int32_t startIndex
= alias
.indexOf(aliasPrefix
, kAliasPrefixLen
, 0);
499 if (startIndex
< 0) {
502 startIndex
+= kAliasPrefixLen
;
503 int32_t endIndex
= alias
.indexOf(solidus
, startIndex
);
505 endIndex
= alias
.length();
507 alias
.extract(startIndex
, endIndex
-startIndex
, aliasedStyle
, kStyleLenMax
+1, US_INV
);
508 aliasedStyle
[kStyleLenMax
] = 0;
511 void handleValueForPattern(ResourceValue
&value
, UnicodeString
&pattern
, UErrorCode
&errorCode
) {
512 if (pattern
.isEmpty()) {
513 if (value
.getType() == URES_ALIAS
) {
514 if (aliasedStyle
[0] == 0) {
515 setAliasedStyle(value
.getAliasUnicodeString(errorCode
));
518 pattern
= value
.getUnicodeString(errorCode
);
523 virtual void put(const char *key
, ResourceValue
&value
, UBool
/*noFallback*/,
524 UErrorCode
&errorCode
) {
526 if (value
.getType() == URES_ALIAS
) {
527 setAliasedStyle(value
.getAliasUnicodeString(errorCode
));
530 ResourceTable listPatterns
= value
.getTable(errorCode
);
531 for (int i
= 0; U_SUCCESS(errorCode
) && listPatterns
.getKeyAndValue(i
, key
, value
); ++i
) {
532 if (uprv_strcmp(key
, "2") == 0) {
533 handleValueForPattern(value
, two
, errorCode
);
534 } else if (uprv_strcmp(key
, "end") == 0) {
535 handleValueForPattern(value
, end
, errorCode
);
536 } else if (uprv_strcmp(key
, "middle") == 0) {
537 handleValueForPattern(value
, middle
, errorCode
);
538 } else if (uprv_strcmp(key
, "start") == 0) {
539 handleValueForPattern(value
, start
, errorCode
);
545 // Virtual destructors must be defined out of line.
546 ListFormatter::ListPatternsSink::~ListPatternsSink() {}
548 ListFormatInternal
* ListFormatter::loadListFormatInternal(
549 const Locale
& locale
, const char * style
, UErrorCode
& errorCode
) {
550 UResourceBundle
* rb
= ures_open(nullptr, locale
.getName(), &errorCode
);
551 rb
= ures_getByKeyWithFallback(rb
, "listPattern", rb
, &errorCode
);
552 if (U_FAILURE(errorCode
)) {
556 ListFormatter::ListPatternsSink sink
;
557 char currentStyle
[kStyleLenMax
+1];
558 uprv_strncpy(currentStyle
, style
, kStyleLenMax
);
559 currentStyle
[kStyleLenMax
] = 0;
562 ures_getAllItemsWithFallback(rb
, currentStyle
, sink
, errorCode
);
563 if (U_FAILURE(errorCode
) || sink
.aliasedStyle
[0] == 0 || uprv_strcmp(currentStyle
, sink
.aliasedStyle
) == 0) {
566 uprv_strcpy(currentStyle
, sink
.aliasedStyle
);
569 if (U_FAILURE(errorCode
)) {
572 if (sink
.two
.isEmpty() || sink
.start
.isEmpty() || sink
.middle
.isEmpty() || sink
.end
.isEmpty()) {
573 errorCode
= U_MISSING_RESOURCE_ERROR
;
577 ListFormatInternal
* result
= new ListFormatInternal(sink
.two
, sink
.start
, sink
.middle
, sink
.end
, locale
, errorCode
);
578 if (result
== nullptr) {
579 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
582 if (U_FAILURE(errorCode
)) {
589 ListFormatter
* ListFormatter::createInstance(UErrorCode
& errorCode
) {
590 Locale locale
; // The default locale.
591 return createInstance(locale
, errorCode
);
594 ListFormatter
* ListFormatter::createInstance(const Locale
& locale
, UErrorCode
& errorCode
) {
595 #if !UCONFIG_NO_FORMATTING
596 return createInstance(locale
, ULISTFMT_TYPE_AND
, ULISTFMT_WIDTH_WIDE
, errorCode
);
598 return createInstance(locale
, "standard", errorCode
);
602 #if !UCONFIG_NO_FORMATTING
603 ListFormatter
* ListFormatter::createInstance(
604 const Locale
& locale
, UListFormatterType type
, UListFormatterWidth width
, UErrorCode
& errorCode
) {
605 const char* style
= typeWidthToStyleString(type
, width
);
606 if (style
== nullptr) {
607 errorCode
= U_ILLEGAL_ARGUMENT_ERROR
;
610 return createInstance(locale
, style
, errorCode
);
614 ListFormatter
* ListFormatter::createInstance(const Locale
& locale
, const char *style
, UErrorCode
& errorCode
) {
615 const ListFormatInternal
* listFormatInternal
= getListFormatInternal(locale
, style
, errorCode
);
616 if (U_FAILURE(errorCode
)) {
619 ListFormatter
* p
= new ListFormatter(listFormatInternal
);
621 errorCode
= U_MEMORY_ALLOCATION_ERROR
;
627 ListFormatter::ListFormatter(const ListFormatData
& listFormatData
, UErrorCode
&errorCode
) {
628 owned
= new ListFormatInternal(listFormatData
, errorCode
);
632 ListFormatter::ListFormatter(const ListFormatInternal
* listFormatterInternal
) : owned(nullptr), data(listFormatterInternal
) {
635 ListFormatter::~ListFormatter() {
640 * Joins first and second using the pattern pat.
641 * On entry offset is an offset into first or -1 if offset unspecified.
642 * On exit offset is offset of second in result if recordOffset was set
643 * Otherwise if it was >=0 it is set to point into result where it used
644 * to point into first. On exit, result is the join of first and second
645 * according to pat. Any previous value of result gets replaced.
647 static void joinStringsAndReplace(
648 const SimpleFormatter
& pat
,
649 const UnicodeString
& first
,
650 const UnicodeString
& second
,
651 UnicodeString
&result
,
654 int32_t *offsetFirst
,
655 int32_t *offsetSecond
,
656 UErrorCode
& errorCode
) {
657 if (U_FAILURE(errorCode
)) {
660 const UnicodeString
*params
[2] = {&first
, &second
};
662 pat
.formatAndReplace(
664 UPRV_LENGTHOF(params
),
667 UPRV_LENGTHOF(offsets
),
669 if (U_FAILURE(errorCode
)) {
672 if (offsets
[0] == -1 || offsets
[1] == -1) {
673 errorCode
= U_INVALID_FORMAT_ERROR
;
678 } else if (offset
>= 0) {
679 offset
+= offsets
[0];
681 if (offsetFirst
!= nullptr) *offsetFirst
= offsets
[0];
682 if (offsetSecond
!= nullptr) *offsetSecond
= offsets
[1];
685 UnicodeString
& ListFormatter::format(
686 const UnicodeString items
[],
688 UnicodeString
& appendTo
,
689 UErrorCode
& errorCode
) const {
691 return format(items
, nItems
, appendTo
, -1, offset
, errorCode
);
694 #if !UCONFIG_NO_FORMATTING
695 UnicodeString
& ListFormatter::format(
696 const UnicodeString items
[],
698 UnicodeString
& appendTo
,
699 FieldPositionIterator
* posIter
,
700 UErrorCode
& errorCode
) const {
702 FieldPositionIteratorHandler
handler(posIter
, errorCode
);
703 return format_(items
, nItems
, appendTo
, -1, offset
, &handler
, errorCode
);
707 UnicodeString
& ListFormatter::format(
708 const UnicodeString items
[],
710 UnicodeString
& appendTo
,
713 UErrorCode
& errorCode
) const {
714 return format_(items
, nItems
, appendTo
, index
, offset
, nullptr, errorCode
);
717 #if !UCONFIG_NO_FORMATTING
718 FormattedList
ListFormatter::formatStringsToValue(
719 const UnicodeString items
[],
721 UErrorCode
& errorCode
) const {
722 LocalPointer
<FormattedListData
> result(new FormattedListData(errorCode
), errorCode
);
723 if (U_FAILURE(errorCode
)) {
724 return FormattedList(errorCode
);
726 UnicodeString string
;
728 auto handler
= result
->getHandler(errorCode
);
729 handler
.setCategory(UFIELD_CATEGORY_LIST
);
730 format_(items
, nItems
, string
, -1, offset
, &handler
, errorCode
);
731 handler
.getError(errorCode
);
732 result
->appendString(string
, errorCode
);
733 if (U_FAILURE(errorCode
)) {
734 return FormattedList(errorCode
);
737 // Add span fields and sort
738 ConstrainedFieldPosition cfpos
;
739 cfpos
.constrainField(UFIELD_CATEGORY_LIST
, ULISTFMT_ELEMENT_FIELD
);
741 handler
.setCategory(UFIELD_CATEGORY_LIST_SPAN
);
742 while (result
->nextPosition(cfpos
, errorCode
)) {
743 handler
.addAttribute(i
++, cfpos
.getStart(), cfpos
.getLimit());
745 handler
.getError(errorCode
);
746 if (U_FAILURE(errorCode
)) {
747 return FormattedList(errorCode
);
751 return FormattedList(result
.orphan());
755 UnicodeString
& ListFormatter::format_(
756 const UnicodeString items
[],
758 UnicodeString
& appendTo
,
761 FieldPositionHandler
* handler
,
762 UErrorCode
& errorCode
) const {
763 #if !UCONFIG_NO_FORMATTING
765 if (U_FAILURE(errorCode
)) {
768 if (data
== nullptr) {
769 errorCode
= U_INVALID_STATE_ERROR
;
778 offset
= appendTo
.length();
780 if (handler
!= nullptr) {
781 handler
->addAttribute(ULISTFMT_ELEMENT_FIELD
,
783 appendTo
.length() + items
[0].length());
785 appendTo
.append(items
[0]);
788 UnicodeString
result(items
[0]);
792 int32_t offsetFirst
= 0;
793 int32_t offsetSecond
= 0;
794 int32_t prefixLength
= 0;
795 // for n items, there are 2 * (n + 1) boundary including 0 and the upper
797 MaybeStackArray
<int32_t, 10> offsets((handler
!= nullptr) ? 2 * (nItems
+ 1): 0);
799 joinStringsAndReplace(
800 data
->patternHandler
->getTwoPattern(items
[0], items
[1]),
810 joinStringsAndReplace(
821 if (handler
!= nullptr) {
823 prefixLength
+= offsetFirst
;
824 offsets
[1] = offsetSecond
- prefixLength
;
827 for (int32_t i
= 2; i
< nItems
- 1; ++i
) {
828 joinStringsAndReplace(
838 if (handler
!= nullptr) {
839 prefixLength
+= offsetFirst
;
840 offsets
[i
] = offsetSecond
- prefixLength
;
843 joinStringsAndReplace(
844 data
->patternHandler
->getEndPattern(items
[nItems
- 1]),
853 if (handler
!= nullptr) {
854 prefixLength
+= offsetFirst
;
855 offsets
[nItems
- 1] = offsetSecond
- prefixLength
;
858 if (handler
!= nullptr) {
859 // If there are already some data in appendTo, we need to adjust the index
860 // by shifting that lenght while insert into handler.
861 int32_t shift
= appendTo
.length() + prefixLength
;
862 // Output the ULISTFMT_ELEMENT_FIELD in the order of the input elements
863 for (int32_t i
= 0; i
< nItems
; ++i
) {
864 offsets
[i
+ nItems
] = offsets
[i
] + items
[i
].length() + shift
;
866 handler
->addAttribute(
867 ULISTFMT_ELEMENT_FIELD
, // id
869 offsets
[i
+ nItems
]); // limit
871 // The locale pattern may reorder the items (such as in ur-IN locale),
872 // so we cannot assume the array is in accendning order.
873 // To handle the edging case, just insert the two ends into the array
874 // and sort. Then we output ULISTFMT_LITERAL_FIELD if the indecies
875 // between the even and odd position are not the same in the sorted array.
876 offsets
[2 * nItems
] = shift
- prefixLength
;
877 offsets
[2 * nItems
+ 1] = result
.length() + shift
- prefixLength
;
878 uprv_sortArray(offsets
.getAlias(), 2 * (nItems
+ 1), sizeof(int32_t),
879 uprv_int32Comparator
, nullptr,
881 for (int32_t i
= 0; i
<= nItems
; ++i
) {
882 if (offsets
[i
* 2] != offsets
[i
* 2 + 1]) {
883 handler
->addAttribute(
884 ULISTFMT_LITERAL_FIELD
, // id
885 offsets
[i
* 2], // index
886 offsets
[i
* 2 + 1]); // limit
890 if (U_SUCCESS(errorCode
)) {
892 offset
+= appendTo
.length();