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, kUndefinedField, 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] == Field(UFIELD_CATEGORY_NUMBER, 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, kUndefinedField, 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 kUndefinedField, which is used for "null field".
100static constexpr Field kEndField = Field(0xf, 0xf);
101
102bool FormattedValueStringBuilderImpl::nextPositionImpl(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
103 int32_t fieldStart = -1;
104 Field currField = kUndefinedField;
105 for (int32_t i = fString.fZero + cfpos.getLimit(); i <= fString.fZero + fString.fLength; i++) {
106 Field _field = (i < fString.fZero + fString.fLength) ? fString.getFieldPtr()[i] : kEndField;
107 // Case 1: currently scanning a field.
108 if (currField != kUndefinedField) {
109 if (currField != _field) {
110 int32_t end = i - fString.fZero;
111 // Grouping separators can be whitespace; don't throw them out!
112 if (currField != Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD)) {
113 end = trimBack(i - fString.fZero);
114 }
115 if (end <= fieldStart) {
116 // Entire field position is ignorable; skip.
117 fieldStart = -1;
118 currField = kUndefinedField;
119 i--; // look at this index again
120 continue;
121 }
122 int32_t start = fieldStart;
123 if (currField != Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD)) {
124 start = trimFront(start);
125 }
126 cfpos.setState(currField.getCategory(), currField.getField(), start, end);
127 return true;
128 }
129 continue;
130 }
131 // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
132 if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
133 && i > fString.fZero
134 // don't return the same field twice in a row:
135 && i - fString.fZero > cfpos.getLimit()
136 && isIntOrGroup(fString.getFieldPtr()[i - 1])
137 && !isIntOrGroup(_field)) {
138 int j = i - 1;
139 for (; j >= fString.fZero && isIntOrGroup(fString.getFieldPtr()[j]); j--) {}
140 cfpos.setState(
141 UFIELD_CATEGORY_NUMBER,
142 UNUM_INTEGER_FIELD,
143 j - fString.fZero + 1,
144 i - fString.fZero);
145 return true;
146 }
147 // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
148 if (numericField != kUndefinedField
149 && cfpos.matchesField(numericField.getCategory(), numericField.getField())
150 && i > fString.fZero
151 // don't return the same field twice in a row:
152 && (i - fString.fZero > cfpos.getLimit()
153 || cfpos.getCategory() != numericField.getCategory()
154 || cfpos.getField() != numericField.getField())
155 && fString.getFieldPtr()[i - 1].isNumeric()
156 && !_field.isNumeric()) {
157 int j = i - 1;
158 for (; j >= fString.fZero && fString.getFieldPtr()[j].isNumeric(); j--) {}
159 cfpos.setState(
160 numericField.getCategory(),
161 numericField.getField(),
162 j - fString.fZero + 1,
163 i - fString.fZero);
164 return true;
165 }
166 // Special case: skip over INTEGER; will be coalesced later.
167 if (_field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
168 _field = kUndefinedField;
169 }
170 // Case 2: no field starting at this position.
171 if (_field.isUndefined() || _field == kEndField) {
172 continue;
173 }
174 // Case 3: check for field starting at this position
175 if (cfpos.matchesField(_field.getCategory(), _field.getField())) {
176 fieldStart = i - fString.fZero;
177 currField = _field;
178 }
179 }
180
181 U_ASSERT(currField == kUndefinedField);
182 return false;
183}
184
185bool FormattedValueStringBuilderImpl::isIntOrGroup(Field field) {
186 return field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
187 || field == Field(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD);
188}
189
190int32_t FormattedValueStringBuilderImpl::trimBack(int32_t limit) const {
191 return unisets::get(unisets::DEFAULT_IGNORABLES)->spanBack(
192 fString.getCharPtr() + fString.fZero,
193 limit,
194 USET_SPAN_CONTAINED);
195}
196
197int32_t FormattedValueStringBuilderImpl::trimFront(int32_t start) const {
198 return start + unisets::get(unisets::DEFAULT_IGNORABLES)->span(
199 fString.getCharPtr() + fString.fZero + start,
200 fString.fLength - start,
201 USET_SPAN_CONTAINED);
202}
203
204
205U_NAMESPACE_END
206
207#endif /* #if !UCONFIG_NO_FORMATTING */
208