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