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     for (int32_t i = fString.fZero + cfpos.getLimit(); i <= fString.fZero + fString.fLength; i++) {
107         Field _field = (i < fString.fZero + fString.fLength) ? fString.getFieldPtr()[i] : kEndField;
108         // Case 1: currently scanning a field.
109         if (currField != kUndefinedField) {
110             if (currField != _field) {
111                 int32_t end = i - fString.fZero;
112                 // Grouping separators can be whitespace; don't throw them out!
113                 if (isTrimmable(currField)) {
114                     end = trimBack(i - fString.fZero);
115                 }
116                 if (end <= fieldStart) {
117                     // Entire field position is ignorable; skip.
118                     fieldStart = -1;
119                     currField = kUndefinedField;
120                     i--;  // look at this index again
121                     continue;
122                 }
123                 int32_t start = fieldStart;
124                 if (isTrimmable(currField)) {
125                     start = trimFront(start);
126                 }
127                 cfpos.setState(currField.getCategory(), currField.getField(), start, end);
128                 return true;
129             }
130             continue;
131         }
132         // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
133         if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
134                 && i > fString.fZero
135                 // don't return the same field twice in a row:
136                 && i - fString.fZero > cfpos.getLimit()
137                 && isIntOrGroup(fString.getFieldPtr()[i - 1])
138                 && !isIntOrGroup(_field)) {
139             int j = i - 1;
140             for (; j >= fString.fZero && isIntOrGroup(fString.getFieldPtr()[j]); j--) {}
141             cfpos.setState(
142                 UFIELD_CATEGORY_NUMBER,
143                 UNUM_INTEGER_FIELD,
144                 j - fString.fZero + 1,
145                 i - fString.fZero);
146             return true;
147         }
148         // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
149         if (numericField != kUndefinedField
150                 && cfpos.matchesField(numericField.getCategory(), numericField.getField())
151                 && i > fString.fZero
152                 // don't return the same field twice in a row:
153                 && (i - fString.fZero > cfpos.getLimit()
154                     || cfpos.getCategory() != numericField.getCategory()
155                     || cfpos.getField() != numericField.getField())
156                 && fString.getFieldPtr()[i - 1].isNumeric()
157                 && !_field.isNumeric()) {
158             // Re-wind to the beginning of the field and then emit it
159             int32_t j = i - 1;
160             for (; j >= fString.fZero && fString.getFieldPtr()[j].isNumeric(); j--) {}
161             cfpos.setState(
162                 numericField.getCategory(),
163                 numericField.getField(),
164                 j - fString.fZero + 1,
165                 i - fString.fZero);
166             return true;
167         }
168         // Special case: emit normalField if we are pointing at the end of spanField.
169         if (i > fString.fZero) {
170             auto elementField = fString.getFieldPtr()[i-1];
171             if (elementField == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)
172                     && cfpos.matchesField(elementField.getCategory(), elementField.getField())
173                     && (cfpos.getLimit() < i - fString.fZero || cfpos.getCategory() != elementField.getCategory())) {
174                 int64_t si = cfpos.getInt64IterationContext() - 1;
175                 cfpos.setState(
176                     elementField.getCategory(),
177                     elementField.getField(),
178                     i - fString.fZero - spanIndices[si].length,
179                     i - fString.fZero);
180                 return true;
181             }
182         }
183         // Special case: skip over INTEGER; will be coalesced later.
184         if (_field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
185             _field = kUndefinedField;
186         }
187         // Case 2: no field starting at this position.
188         if (_field.isUndefined() || _field == kEndField) {
189             continue;
190         }
191         // Case 3: check for field starting at this position
192         // Case 3a: Need to add a SpanField
193         if (_field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
194             int64_t si = cfpos.getInt64IterationContext();
195             int32_t spanValue = spanIndices[si].spanValue;
196             int32_t length = spanIndices[si].length;
197             cfpos.setInt64IterationContext(si + 1);
198             if (cfpos.matchesField(UFIELD_CATEGORY_LIST_SPAN, spanValue)) {
199                 UFieldCategory spanCategory = UFIELD_CATEGORY_LIST_SPAN;
200                 fieldStart = i - fString.fZero;
201                 int32_t end = fieldStart + length;
202                 cfpos.setState(
203                     spanCategory,
204                     spanValue,
205                     fieldStart,
206                     end);
207                 return true;
208             } else {
209                 // Failed to match; jump ahead
210                 i += length - 1;
211                 continue;
212             }
213         }
214         // Case 3b: No SpanField
215         if (cfpos.matchesField(_field.getCategory(), _field.getField())) {
216             fieldStart = i - fString.fZero;
217             currField = _field;
218         }
219     }
220 
221     U_ASSERT(currField == kUndefinedField);
222     // Always set the position to the end so that we don't revisit previous sections
223     cfpos.setState(
224         cfpos.getCategory(),
225         cfpos.getField(),
226         fString.fLength,
227         fString.fLength);
228     return false;
229 }
230 
appendSpanInfo(int32_t spanValue,int32_t length,UErrorCode & status)231 void FormattedValueStringBuilderImpl::appendSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status) {
232     if (U_FAILURE(status)) { return; }
233     U_ASSERT(spanIndices.getCapacity() >= spanValue);
234     if (spanIndices.getCapacity() == spanValue) {
235         if (!spanIndices.resize(spanValue * 2, spanValue)) {
236             status = U_MEMORY_ALLOCATION_ERROR;
237             return;
238         }
239     }
240     spanIndices[spanValue] = {spanValue, length};
241 }
242 
prependSpanInfo(int32_t spanValue,int32_t length,UErrorCode & status)243 void FormattedValueStringBuilderImpl::prependSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status) {
244     if (U_FAILURE(status)) { return; }
245     U_ASSERT(spanIndices.getCapacity() >= spanValue);
246     if (spanIndices.getCapacity() == spanValue) {
247         if (!spanIndices.resize(spanValue * 2, spanValue)) {
248             status = U_MEMORY_ALLOCATION_ERROR;
249             return;
250         }
251     }
252     for (int32_t i = spanValue - 1; i >= 0; i--) {
253         spanIndices[i+1] = spanIndices[i];
254     }
255     spanIndices[0] = {spanValue, length};
256 }
257 
isIntOrGroup(Field field)258 bool FormattedValueStringBuilderImpl::isIntOrGroup(Field field) {
259     return field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
260         || field == Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD);
261 }
262 
isTrimmable(Field field)263 bool FormattedValueStringBuilderImpl::isTrimmable(Field field) {
264     return field != Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD)
265         && field.getCategory() != UFIELD_CATEGORY_LIST;
266 }
267 
trimBack(int32_t limit) const268 int32_t FormattedValueStringBuilderImpl::trimBack(int32_t limit) const {
269     return unisets::get(unisets::DEFAULT_IGNORABLES)->spanBack(
270         fString.getCharPtr() + fString.fZero,
271         limit,
272         USET_SPAN_CONTAINED);
273 }
274 
trimFront(int32_t start) const275 int32_t FormattedValueStringBuilderImpl::trimFront(int32_t start) const {
276     return start + unisets::get(unisets::DEFAULT_IGNORABLES)->span(
277         fString.getCharPtr() + fString.fZero + start,
278         fString.fLength - start,
279         USET_SPAN_CONTAINED);
280 }
281 
282 
283 U_NAMESPACE_END
284 
285 #endif /* #if !UCONFIG_NO_FORMATTING */
286