1 // © 2018 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 // This file contains one implementation of FormattedValue.
9 // Other independent implementations should go into their own cpp file for
10 // better dependency modularization.
11 
12 #include "unicode/ustring.h"
13 #include "formattedval_impl.h"
14 #include "number_types.h"
15 #include "formatted_string_builder.h"
16 #include "number_utils.h"
17 #include "static_unicode_sets.h"
18 #include "unicode/listformatter.h"
19 
20 U_NAMESPACE_BEGIN
21 
22 
23 typedef FormattedStringBuilder::Field Field;
24 
25 
FormattedValueStringBuilderImpl(Field numericField)26 FormattedValueStringBuilderImpl::FormattedValueStringBuilderImpl(Field numericField)
27         : fNumericField(numericField) {
28 }
29 
~FormattedValueStringBuilderImpl()30 FormattedValueStringBuilderImpl::~FormattedValueStringBuilderImpl() {
31 }
32 
33 
toString(UErrorCode &) const34 UnicodeString FormattedValueStringBuilderImpl::toString(UErrorCode&) const {
35     return fString.toUnicodeString();
36 }
37 
toTempString(UErrorCode &) const38 UnicodeString FormattedValueStringBuilderImpl::toTempString(UErrorCode&) const {
39     return fString.toTempUnicodeString();
40 }
41 
appendTo(Appendable & appendable,UErrorCode &) const42 Appendable& FormattedValueStringBuilderImpl::appendTo(Appendable& appendable, UErrorCode&) const {
43     appendable.appendString(fString.chars(), fString.length());
44     return appendable;
45 }
46 
nextPosition(ConstrainedFieldPosition & cfpos,UErrorCode & status) const47 UBool FormattedValueStringBuilderImpl::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
48     // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
49     return nextPositionImpl(cfpos, fNumericField, status) ? true : false;
50 }
51 
nextFieldPosition(FieldPosition & fp,UErrorCode & status) const52 UBool FormattedValueStringBuilderImpl::nextFieldPosition(FieldPosition& fp, UErrorCode& status) const {
53     int32_t rawField = fp.getField();
54 
55     if (rawField == FieldPosition::DONT_CARE) {
56         return false;
57     }
58 
59     if (rawField < 0 || rawField >= UNUM_FIELD_COUNT) {
60         status = U_ILLEGAL_ARGUMENT_ERROR;
61         return false;
62     }
63 
64     ConstrainedFieldPosition cfpos;
65     cfpos.constrainField(UFIELD_CATEGORY_NUMBER, rawField);
66     cfpos.setState(UFIELD_CATEGORY_NUMBER, rawField, fp.getBeginIndex(), fp.getEndIndex());
67     if (nextPositionImpl(cfpos, kUndefinedField, status)) {
68         fp.setBeginIndex(cfpos.getStart());
69         fp.setEndIndex(cfpos.getLimit());
70         return true;
71     }
72 
73     // Special case: fraction should start after integer if fraction is not present
74     if (rawField == UNUM_FRACTION_FIELD && fp.getEndIndex() == 0) {
75         bool inside = false;
76         int32_t i = fString.fZero;
77         for (; i < fString.fZero + fString.fLength; i++) {
78             if (isIntOrGroup(fString.getFieldPtr()[i]) || fString.getFieldPtr()[i] == Field(UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD)) {
79                 inside = true;
80             } else if (inside) {
81                 break;
82             }
83         }
84         fp.setBeginIndex(i - fString.fZero);
85         fp.setEndIndex(i - fString.fZero);
86     }
87 
88     return false;
89 }
90 
getAllFieldPositions(FieldPositionIteratorHandler & fpih,UErrorCode & status) const91 void FormattedValueStringBuilderImpl::getAllFieldPositions(FieldPositionIteratorHandler& fpih,
92                                                UErrorCode& status) const {
93     ConstrainedFieldPosition cfpos;
94     while (nextPositionImpl(cfpos, kUndefinedField, status)) {
95         fpih.addAttribute(cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
96     }
97 }
98 
99 // Signal the end of the string using a field that doesn't exist and that is
100 // different from kUndefinedField, which is used for "null field".
101 static constexpr Field kEndField = Field(0xf, 0xf);
102 
nextPositionImpl(ConstrainedFieldPosition & cfpos,Field numericField,UErrorCode &) const103 bool FormattedValueStringBuilderImpl::nextPositionImpl(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
104     int32_t fieldStart = -1;
105     Field currField = kUndefinedField;
106     bool prevIsSpan = false;
107     int32_t nextSpanStart = -1;
108     if (spanIndicesCount > 0) {
109         int64_t si = cfpos.getInt64IterationContext();
110         U_ASSERT(si <= spanIndicesCount);
111         if (si < spanIndicesCount) {
112             nextSpanStart = spanIndices[si].start;
113         }
114         if (si > 0) {
115             prevIsSpan = cfpos.getCategory() == spanIndices[si-1].category
116                 && cfpos.getField() == spanIndices[si-1].spanValue;
117         }
118     }
119     bool prevIsNumeric = false;
120     if (numericField != kUndefinedField) {
121         prevIsNumeric = cfpos.getCategory() == numericField.getCategory()
122             && cfpos.getField() == numericField.getField();
123     }
124     bool prevIsInteger = cfpos.getCategory() == UFIELD_CATEGORY_NUMBER
125         && cfpos.getField() == UNUM_INTEGER_FIELD;
126 
127     for (int32_t i = fString.fZero + cfpos.getLimit(); i <= fString.fZero + fString.fLength; i++) {
128         Field _field = (i < fString.fZero + fString.fLength) ? fString.getFieldPtr()[i] : kEndField;
129         // Case 1: currently scanning a field.
130         if (currField != kUndefinedField) {
131             if (currField != _field) {
132                 int32_t end = i - fString.fZero;
133                 // Grouping separators can be whitespace; don't throw them out!
134                 if (isTrimmable(currField)) {
135                     end = trimBack(i - fString.fZero);
136                 }
137                 if (end <= fieldStart) {
138                     // Entire field position is ignorable; skip.
139                     fieldStart = -1;
140                     currField = kUndefinedField;
141                     i--;  // look at this index again
142                     continue;
143                 }
144                 int32_t start = fieldStart;
145                 if (isTrimmable(currField)) {
146                     start = trimFront(start);
147                 }
148                 cfpos.setState(currField.getCategory(), currField.getField(), start, end);
149                 return true;
150             }
151             continue;
152         }
153         // Special case: emit normalField if we are pointing at the end of spanField.
154         if (i > fString.fZero && prevIsSpan) {
155             int64_t si = cfpos.getInt64IterationContext() - 1;
156             U_ASSERT(si >= 0);
157             int32_t previ = i - spanIndices[si].length;
158             U_ASSERT(previ >= fString.fZero);
159             Field prevField = fString.getFieldPtr()[previ];
160             if (prevField == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
161                 // Special handling for ULISTFMT_ELEMENT_FIELD
162                 if (cfpos.matchesField(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
163                     fieldStart = i - fString.fZero - spanIndices[si].length;
164                     int32_t end = fieldStart + spanIndices[si].length;
165                     cfpos.setState(
166                         UFIELD_CATEGORY_LIST,
167                         ULISTFMT_ELEMENT_FIELD,
168                         fieldStart,
169                         end);
170                     return true;
171                 } else {
172                     prevIsSpan = false;
173                 }
174             } else {
175                 // Re-wind, since there may be multiple fields in the span.
176                 i = previ;
177                 _field = prevField;
178             }
179         }
180         // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
181         if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
182                 && i > fString.fZero
183                 && !prevIsInteger
184                 && !prevIsNumeric
185                 && isIntOrGroup(fString.getFieldPtr()[i - 1])
186                 && !isIntOrGroup(_field)) {
187             int j = i - 1;
188             for (; j >= fString.fZero && isIntOrGroup(fString.getFieldPtr()[j]); j--) {}
189             cfpos.setState(
190                 UFIELD_CATEGORY_NUMBER,
191                 UNUM_INTEGER_FIELD,
192                 j - fString.fZero + 1,
193                 i - fString.fZero);
194             return true;
195         }
196         // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
197         if (numericField != kUndefinedField
198                 && cfpos.matchesField(numericField.getCategory(), numericField.getField())
199                 && i > fString.fZero
200                 && !prevIsNumeric
201                 && fString.getFieldPtr()[i - 1].isNumeric()
202                 && !_field.isNumeric()) {
203             // Re-wind to the beginning of the field and then emit it
204             int32_t j = i - 1;
205             for (; j >= fString.fZero && fString.getFieldPtr()[j].isNumeric(); j--) {}
206             cfpos.setState(
207                 numericField.getCategory(),
208                 numericField.getField(),
209                 j - fString.fZero + 1,
210                 i - fString.fZero);
211             return true;
212         }
213         // Check for span field
214         if (!prevIsSpan && (
215                 _field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD) ||
216                 i - fString.fZero == nextSpanStart)) {
217             int64_t si = cfpos.getInt64IterationContext();
218             if (si >= spanIndicesCount) {
219                 break;
220             }
221             UFieldCategory spanCategory = spanIndices[si].category;
222             int32_t spanValue = spanIndices[si].spanValue;
223             int32_t length = spanIndices[si].length;
224             cfpos.setInt64IterationContext(si + 1);
225             if (si + 1 < spanIndicesCount) {
226                 nextSpanStart = spanIndices[si + 1].start;
227             }
228             if (cfpos.matchesField(spanCategory, spanValue)) {
229                 fieldStart = i - fString.fZero;
230                 int32_t end = fieldStart + length;
231                 cfpos.setState(
232                     spanCategory,
233                     spanValue,
234                     fieldStart,
235                     end);
236                 return true;
237             } else if (_field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
238                 // Special handling for ULISTFMT_ELEMENT_FIELD
239                 if (cfpos.matchesField(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
240                     fieldStart = i - fString.fZero;
241                     int32_t end = fieldStart + length;
242                     cfpos.setState(
243                         UFIELD_CATEGORY_LIST,
244                         ULISTFMT_ELEMENT_FIELD,
245                         fieldStart,
246                         end);
247                     return true;
248                 } else {
249                     // Failed to match; jump ahead
250                     i += length - 1;
251                     // goto loopend
252                 }
253             }
254         }
255         // Special case: skip over INTEGER; will be coalesced later.
256         else if (_field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
257             _field = kUndefinedField;
258         }
259         // No field starting at this position.
260         else if (_field.isUndefined() || _field == kEndField) {
261             // goto loopend
262         }
263         // No SpanField
264         else if (cfpos.matchesField(_field.getCategory(), _field.getField())) {
265             fieldStart = i - fString.fZero;
266             currField = _field;
267         }
268         // loopend:
269         prevIsSpan = false;
270         prevIsNumeric = false;
271         prevIsInteger = false;
272     }
273 
274     U_ASSERT(currField == kUndefinedField);
275     // Always set the position to the end so that we don't revisit previous sections
276     cfpos.setState(
277         cfpos.getCategory(),
278         cfpos.getField(),
279         fString.fLength,
280         fString.fLength);
281     return false;
282 }
283 
appendSpanInfo(UFieldCategory category,int32_t spanValue,int32_t start,int32_t length,UErrorCode & status)284 void FormattedValueStringBuilderImpl::appendSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status) {
285     if (U_FAILURE(status)) { return; }
286     U_ASSERT(spanIndices.getCapacity() >= spanValue);
287     if (spanIndices.getCapacity() == spanValue) {
288         if (!spanIndices.resize(spanValue * 2, spanValue)) {
289             status = U_MEMORY_ALLOCATION_ERROR;
290             return;
291         }
292     }
293     spanIndices[spanValue] = {category, spanValue, start, length};
294     spanIndicesCount++;
295 }
296 
prependSpanInfo(UFieldCategory category,int32_t spanValue,int32_t start,int32_t length,UErrorCode & status)297 void FormattedValueStringBuilderImpl::prependSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status) {
298     if (U_FAILURE(status)) { return; }
299     U_ASSERT(spanIndices.getCapacity() >= spanValue);
300     if (spanIndices.getCapacity() == spanValue) {
301         if (!spanIndices.resize(spanValue * 2, spanValue)) {
302             status = U_MEMORY_ALLOCATION_ERROR;
303             return;
304         }
305     }
306     for (int32_t i = spanValue - 1; i >= 0; i--) {
307         spanIndices[i+1] = spanIndices[i];
308     }
309     spanIndices[0] = {category, spanValue, start, length};
310     spanIndicesCount++;
311 }
312 
isIntOrGroup(Field field)313 bool FormattedValueStringBuilderImpl::isIntOrGroup(Field field) {
314     return field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
315         || field == Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD);
316 }
317 
isTrimmable(Field field)318 bool FormattedValueStringBuilderImpl::isTrimmable(Field field) {
319     return field != Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD)
320         && field.getCategory() != UFIELD_CATEGORY_LIST;
321 }
322 
trimBack(int32_t limit) const323 int32_t FormattedValueStringBuilderImpl::trimBack(int32_t limit) const {
324     return unisets::get(unisets::DEFAULT_IGNORABLES)->spanBack(
325         fString.getCharPtr() + fString.fZero,
326         limit,
327         USET_SPAN_CONTAINED);
328 }
329 
trimFront(int32_t start) const330 int32_t FormattedValueStringBuilderImpl::trimFront(int32_t start) const {
331     return start + unisets::get(unisets::DEFAULT_IGNORABLES)->span(
332         fString.getCharPtr() + fString.fZero + start,
333         fString.fLength - start,
334         USET_SPAN_CONTAINED);
335 }
336 
337 
338 U_NAMESPACE_END
339 
340 #endif /* #if !UCONFIG_NO_FORMATTING */
341