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
19U_NAMESPACE_BEGIN
20
21
22typedef FormattedStringBuilder::Field Field;
23
24
25FormattedValueStringBuilderImpl::FormattedValueStringBuilderImpl(Field numericField)
26 : fNumericField(numericField) {
27}
28
29FormattedValueStringBuilderImpl::~FormattedValueStringBuilderImpl() {
30}
31
32
33UnicodeString FormattedValueStringBuilderImpl::toString(UErrorCode&) const {
34 return fString.toUnicodeString();
35}
36
37UnicodeString FormattedValueStringBuilderImpl::toTempString(UErrorCode&) const {
38 return fString.toTempUnicodeString();
39}
40
41Appendable& FormattedValueStringBuilderImpl::appendTo(Appendable& appendable, UErrorCode&) const {
42 appendable.appendString(fString.chars(), fString.length());
43 return appendable;
44}
45
46UBool FormattedValueStringBuilderImpl::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
47 // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
48 return nextPositionImpl(cfpos, fNumericField, status) ? TRUE : FALSE;
49}
50
51UBool FormattedValueStringBuilderImpl::nextFieldPosition(FieldPosition& fp, UErrorCode& status) const {
52 int32_t rawField = fp.getField();
53
54 if (rawField == FieldPosition::DONT_CARE) {
55 return FALSE;
56 }
57
58 if (rawField < 0 || rawField >= UNUM_FIELD_COUNT) {
59 status = U_ILLEGAL_ARGUMENT_ERROR;
60 return FALSE;
61 }
62
63 ConstrainedFieldPosition cfpos;
64 cfpos.constrainField(UFIELD_CATEGORY_NUMBER, rawField);
65 cfpos.setState(UFIELD_CATEGORY_NUMBER, rawField, fp.getBeginIndex(), fp.getEndIndex());
66 if (nextPositionImpl(cfpos, 0, status)) {
67 fp.setBeginIndex(cfpos.getStart());
68 fp.setEndIndex(cfpos.getLimit());
69 return TRUE;
70 }
71
72 // Special case: fraction should start after integer if fraction is not present
73 if (rawField == UNUM_FRACTION_FIELD && fp.getEndIndex() == 0) {
74 bool inside = false;
75 int32_t i = fString.fZero;
76 for (; i < fString.fZero + fString.fLength; i++) {
77 if (isIntOrGroup(fString.getFieldPtr()[i]) || fString.getFieldPtr()[i] == UNUM_DECIMAL_SEPARATOR_FIELD) {
78 inside = true;
79 } else if (inside) {
80 break;
81 }
82 }
83 fp.setBeginIndex(i - fString.fZero);
84 fp.setEndIndex(i - fString.fZero);
85 }
86
87 return FALSE;
88}
89
90void FormattedValueStringBuilderImpl::getAllFieldPositions(FieldPositionIteratorHandler& fpih,
91 UErrorCode& status) const {
92 ConstrainedFieldPosition cfpos;
93 while (nextPositionImpl(cfpos, 0, status)) {
94 fpih.addAttribute(cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
95 }
96}
97
98// Signal the end of the string using a field that doesn't exist and that is
99// different from UNUM_FIELD_COUNT, which is used for "null number field".
100static constexpr Field kEndField = 0xff;
101
102bool FormattedValueStringBuilderImpl::nextPositionImpl(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
103 auto numericCAF = StringBuilderFieldUtils::expand(numericField);
104 int32_t fieldStart = -1;
105 Field currField = UNUM_FIELD_COUNT;
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 != UNUM_FIELD_COUNT) {
110 if (currField != _field) {
111 int32_t end = i - fString.fZero;
112 // Grouping separators can be whitespace; don't throw them out!
113 if (currField != UNUM_GROUPING_SEPARATOR_FIELD) {
114 end = trimBack(i - fString.fZero);
115 }
116 if (end <= fieldStart) {
117 // Entire field position is ignorable; skip.
118 fieldStart = -1;
119 currField = UNUM_FIELD_COUNT;
120 i--; // look at this index again
121 continue;
122 }
123 int32_t start = fieldStart;
124 if (currField != UNUM_GROUPING_SEPARATOR_FIELD) {
125 start = trimFront(start);
126 }
127 auto caf = StringBuilderFieldUtils::expand(currField);
128 cfpos.setState(caf.category, caf.field, start, end);
129 return true;
130 }
131 continue;
132 }
133 // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
134 if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
135 && i > fString.fZero
136 // don't return the same field twice in a row:
137 && i - fString.fZero > cfpos.getLimit()
138 && isIntOrGroup(fString.getFieldPtr()[i - 1])
139 && !isIntOrGroup(_field)) {
140 int j = i - 1;
141 for (; j >= fString.fZero && isIntOrGroup(fString.getFieldPtr()[j]); j--) {}
142 cfpos.setState(
143 UFIELD_CATEGORY_NUMBER,
144 UNUM_INTEGER_FIELD,
145 j - fString.fZero + 1,
146 i - fString.fZero);
147 return true;
148 }
149 // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
150 if (numericField != 0
151 && cfpos.matchesField(numericCAF.category, numericCAF.field)
152 && i > fString.fZero
153 // don't return the same field twice in a row:
154 && (i - fString.fZero > cfpos.getLimit()
155 || cfpos.getCategory() != numericCAF.category
156 || cfpos.getField() != numericCAF.field)
157 && isNumericField(fString.getFieldPtr()[i - 1])
158 && !isNumericField(_field)) {
159 int j = i - 1;
160 for (; j >= fString.fZero && isNumericField(fString.getFieldPtr()[j]); j--) {}
161 cfpos.setState(
162 numericCAF.category,
163 numericCAF.field,
164 j - fString.fZero + 1,
165 i - fString.fZero);
166 return true;
167 }
168 // Special case: skip over INTEGER; will be coalesced later.
169 if (_field == UNUM_INTEGER_FIELD) {
170 _field = UNUM_FIELD_COUNT;
171 }
172 // Case 2: no field starting at this position.
173 if (_field == UNUM_FIELD_COUNT || _field == kEndField) {
174 continue;
175 }
176 // Case 3: check for field starting at this position
177 auto caf = StringBuilderFieldUtils::expand(_field);
178 if (cfpos.matchesField(caf.category, caf.field)) {
179 fieldStart = i - fString.fZero;
180 currField = _field;
181 }
182 }
183
184 U_ASSERT(currField == UNUM_FIELD_COUNT);
185 return false;
186}
187
188bool FormattedValueStringBuilderImpl::isIntOrGroup(Field field) {
189 return field == UNUM_INTEGER_FIELD
190 || field == UNUM_GROUPING_SEPARATOR_FIELD;
191}
192
193bool FormattedValueStringBuilderImpl::isNumericField(Field field) {
194 return StringBuilderFieldUtils::isNumericField(field);
195}
196
197int32_t FormattedValueStringBuilderImpl::trimBack(int32_t limit) const {
198 return unisets::get(unisets::DEFAULT_IGNORABLES)->spanBack(
199 fString.getCharPtr() + fString.fZero,
200 limit,
201 USET_SPAN_CONTAINED);
202}
203
204int32_t FormattedValueStringBuilderImpl::trimFront(int32_t start) const {
205 return start + unisets::get(unisets::DEFAULT_IGNORABLES)->span(
206 fString.getCharPtr() + fString.fZero + start,
207 fString.fLength - start,
208 USET_SPAN_CONTAINED);
209}
210
211
212U_NAMESPACE_END
213
214#endif /* #if !UCONFIG_NO_FORMATTING */
215