Coverage Report

Created: 2025-11-07 06:50

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