// Internal struct that must be exported for MSVC
struct U_I18N_API SpanInfo {
+ UFieldCategory category;
int32_t spanValue;
+ int32_t start;
int32_t length;
};
/**
* Adds additional metadata used for span fields.
- *
- * spanValue: the index of the list item, for example.
+ *
+ * category: the category to use for the span field.
+ * spanValue: the value of the span field: index of the list item, for example.
+ * start: the start position within the string of the span. -1 if unknown.
* length: the length of the span, used to split adjacent fields.
*/
- void appendSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status);
- void prependSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status);
+ void appendSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status);
+ void prependSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status);
private:
FormattedStringBuilder fString;
FormattedStringBuilder::Field fNumericField;
MaybeStackArray<SpanInfo, 8> spanIndices;
+ int32_t spanIndicesCount = 0;
bool nextPositionImpl(ConstrainedFieldPosition& cfpos, FormattedStringBuilder::Field numericField, UErrorCode& status) const;
static bool isIntOrGroup(FormattedStringBuilder::Field field);
bool FormattedValueStringBuilderImpl::nextPositionImpl(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
int32_t fieldStart = -1;
Field currField = kUndefinedField;
+ bool prevIsSpan = false;
+ int32_t nextSpanStart = -1;
+ if (spanIndicesCount > 0) {
+ int64_t si = cfpos.getInt64IterationContext();
+ U_ASSERT(si <= spanIndicesCount);
+ if (si < spanIndicesCount) {
+ nextSpanStart = spanIndices[si].start;
+ }
+ if (si > 0) {
+ prevIsSpan = cfpos.getCategory() == spanIndices[si-1].category
+ && cfpos.getField() == spanIndices[si-1].spanValue;
+ }
+ }
+ bool prevIsNumeric = false;
+ if (numericField != kUndefinedField) {
+ prevIsNumeric = cfpos.getCategory() == numericField.getCategory()
+ && cfpos.getField() == numericField.getField();
+ }
+ bool prevIsInteger = cfpos.getCategory() == UFIELD_CATEGORY_NUMBER
+ && cfpos.getField() == UNUM_INTEGER_FIELD;
+
for (int32_t i = fString.fZero + cfpos.getLimit(); i <= fString.fZero + fString.fLength; i++) {
Field _field = (i < fString.fZero + fString.fLength) ? fString.getFieldPtr()[i] : kEndField;
// Case 1: currently scanning a field.
}
continue;
}
+ // Special case: emit normalField if we are pointing at the end of spanField.
+ if (i > fString.fZero && prevIsSpan) {
+ int64_t si = cfpos.getInt64IterationContext() - 1;
+ U_ASSERT(si >= 0);
+ if (_field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
+ // Special handling for ULISTFMT_ELEMENT_FIELD
+ if (cfpos.matchesField(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
+ fieldStart = i - fString.fZero - spanIndices[si].length;
+ int32_t end = fieldStart + spanIndices[si].length;
+ cfpos.setState(
+ UFIELD_CATEGORY_LIST,
+ ULISTFMT_ELEMENT_FIELD,
+ fieldStart,
+ end);
+ return true;
+ }
+ } else {
+ // Re-wind, since there may be multiple fields in the span.
+ i -= spanIndices[si].length;
+ U_ASSERT(i >= fString.fZero);
+ _field = fString.getFieldPtr()[i];
+ }
+ }
// Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
&& i > fString.fZero
- // don't return the same field twice in a row:
- && i - fString.fZero > cfpos.getLimit()
+ && !prevIsInteger
+ && !prevIsNumeric
&& isIntOrGroup(fString.getFieldPtr()[i - 1])
&& !isIntOrGroup(_field)) {
int j = i - 1;
if (numericField != kUndefinedField
&& cfpos.matchesField(numericField.getCategory(), numericField.getField())
&& i > fString.fZero
- // don't return the same field twice in a row:
- && (i - fString.fZero > cfpos.getLimit()
- || cfpos.getCategory() != numericField.getCategory()
- || cfpos.getField() != numericField.getField())
+ && !prevIsNumeric
&& fString.getFieldPtr()[i - 1].isNumeric()
&& !_field.isNumeric()) {
// Re-wind to the beginning of the field and then emit it
i - fString.fZero);
return true;
}
- // Special case: emit normalField if we are pointing at the end of spanField.
- if (i > fString.fZero) {
- auto elementField = fString.getFieldPtr()[i-1];
- if (elementField == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)
- && cfpos.matchesField(elementField.getCategory(), elementField.getField())
- && (
- cfpos.getLimit() < i - fString.fZero
- || cfpos.getCategory() != elementField.getCategory()
- || cfpos.getField() != elementField.getField())) {
- int64_t si = cfpos.getInt64IterationContext() - 1;
- cfpos.setState(
- elementField.getCategory(),
- elementField.getField(),
- i - fString.fZero - spanIndices[si].length,
- i - fString.fZero);
- return true;
- }
- }
- // Special case: skip over INTEGER; will be coalesced later.
- if (_field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
- _field = kUndefinedField;
- }
- // Case 2: no field starting at this position.
- if (_field.isUndefined() || _field == kEndField) {
- continue;
- }
- // Case 3: check for field starting at this position
- // Case 3a: Need to add a SpanField
- if (_field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
+ // Check for span field
+ if (!prevIsSpan && (
+ _field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD) ||
+ i - fString.fZero == nextSpanStart)) {
int64_t si = cfpos.getInt64IterationContext();
+ if (si >= spanIndicesCount) {
+ break;
+ }
+ UFieldCategory spanCategory = spanIndices[si].category;
int32_t spanValue = spanIndices[si].spanValue;
int32_t length = spanIndices[si].length;
cfpos.setInt64IterationContext(si + 1);
- if (cfpos.matchesField(UFIELD_CATEGORY_LIST_SPAN, spanValue)) {
- UFieldCategory spanCategory = UFIELD_CATEGORY_LIST_SPAN;
+ if (si + 1 < spanIndicesCount) {
+ nextSpanStart = spanIndices[si + 1].start;
+ }
+ if (cfpos.matchesField(spanCategory, spanValue)) {
fieldStart = i - fString.fZero;
int32_t end = fieldStart + length;
cfpos.setState(
fieldStart,
end);
return true;
- } else {
- // Failed to match; jump ahead
- i += length - 1;
- continue;
+ } else if (_field == Field(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
+ // Special handling for ULISTFMT_ELEMENT_FIELD
+ if (cfpos.matchesField(UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD)) {
+ fieldStart = i - fString.fZero;
+ int32_t end = fieldStart + length;
+ cfpos.setState(
+ UFIELD_CATEGORY_LIST,
+ ULISTFMT_ELEMENT_FIELD,
+ fieldStart,
+ end);
+ return true;
+ } else {
+ // Failed to match; jump ahead
+ i += length - 1;
+ // goto loopend
+ }
}
}
- // Case 3b: No SpanField
- if (cfpos.matchesField(_field.getCategory(), _field.getField())) {
+ // Special case: skip over INTEGER; will be coalesced later.
+ else if (_field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
+ _field = kUndefinedField;
+ }
+ // No field starting at this position.
+ else if (_field.isUndefined() || _field == kEndField) {
+ // goto loopend
+ }
+ // No SpanField
+ else if (cfpos.matchesField(_field.getCategory(), _field.getField())) {
fieldStart = i - fString.fZero;
currField = _field;
}
+ // loopend:
+ prevIsSpan = false;
+ prevIsNumeric = false;
+ prevIsInteger = false;
}
U_ASSERT(currField == kUndefinedField);
return false;
}
-void FormattedValueStringBuilderImpl::appendSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status) {
+void FormattedValueStringBuilderImpl::appendSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
U_ASSERT(spanIndices.getCapacity() >= spanValue);
if (spanIndices.getCapacity() == spanValue) {
return;
}
}
- spanIndices[spanValue] = {spanValue, length};
+ spanIndices[spanValue] = {category, spanValue, start, length};
+ spanIndicesCount++;
}
-void FormattedValueStringBuilderImpl::prependSpanInfo(int32_t spanValue, int32_t length, UErrorCode& status) {
+void FormattedValueStringBuilderImpl::prependSpanInfo(UFieldCategory category, int32_t spanValue, int32_t start, int32_t length, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
U_ASSERT(spanIndices.getCapacity() >= spanValue);
if (spanIndices.getCapacity() == spanValue) {
for (int32_t i = spanValue - 1; i >= 0; i--) {
spanIndices[i+1] = spanIndices[i];
}
- spanIndices[0] = {spanValue, length};
+ spanIndices[0] = {category, spanValue, start, length};
+ spanIndicesCount++;
}
bool FormattedValueStringBuilderImpl::isIntOrGroup(Field field) {
start,
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD},
status);
- data->appendSpanInfo(0, start.length(), status);
+ data->appendSpanInfo(UFIELD_CATEGORY_LIST_SPAN, 0, -1, start.length(), status);
}
}
next,
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD},
status);
- data->appendSpanInfo(position, next.length(), status);
+ data->appendSpanInfo(UFIELD_CATEGORY_LIST_SPAN, position, -1, next.length(), status);
data->getStringRef().append(
temp.tempSubString(offsets[1]),
{UFIELD_CATEGORY_LIST, ULISTFMT_LITERAL_FIELD},
next,
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD},
status);
- data->prependSpanInfo(position, next.length(), status);
+ data->prependSpanInfo(UFIELD_CATEGORY_LIST_SPAN, position, -1, next.length(), status);
data->getStringRef().insert(
0,
temp.tempSubStringBetween(0, offsets[1]),
#define UPRV_INDEX_1 (lengthPrefix + length1)
#define UPRV_INDEX_2 (lengthPrefix + length1 + lengthInfix)
#define UPRV_INDEX_3 (lengthPrefix + length1 + lengthInfix + length2)
+ #define UPRV_INDEX_4 (lengthPrefix + length1 + lengthInfix + length2 + lengthSuffix)
int32_t lengthRange = SimpleModifier::formatTwoArgPattern(
fRangeFormatter,
// TODO: Support padding?
if (collapseInner) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modInner, *micros2.modInner);
- lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ lengthSuffix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_4, status);
+ lengthPrefix += mod.getPrefixLength();
+ lengthSuffix -= mod.getPrefixLength();
} else {
length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
- length2 += micros2.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ length2 += micros2.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_4, status);
}
if (collapseMiddle) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modMiddle, *micros2.modMiddle);
- lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ lengthSuffix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_4, status);
+ lengthPrefix += mod.getPrefixLength();
+ lengthSuffix -= mod.getPrefixLength();
} else {
length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
- length2 += micros2.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ length2 += micros2.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_4, status);
}
if (collapseOuter) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modOuter, *micros2.modOuter);
- lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
+ lengthSuffix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_4, status);
+ lengthPrefix += mod.getPrefixLength();
+ lengthSuffix -= mod.getPrefixLength();
} else {
length1 += micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
- length2 += micros2.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
+ length2 += micros2.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_4, status);
}
+
+ // Now that all pieces are added, save the span info.
+ data.appendSpanInfo(UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, UPRV_INDEX_0, length1, status);
+ data.appendSpanInfo(UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, UPRV_INDEX_2, length2, status);
}
*/
UFIELD_CATEGORY_DATE_INTERVAL_SPAN = 0x1000 + UFIELD_CATEGORY_DATE_INTERVAL,
+#ifndef U_HIDE_DRAFT_API
+ /**
+ * Category for spans in a number range.
+ *
+ * @draft ICU 69
+ */
+ UFIELD_CATEGORY_NUMBER_RANGE_SPAN = 0x1000 + UFIELD_CATEGORY_NUMBER,
+#endif // U_HIDE_DRAFT_API
+
} UFieldCategory;
if (assertSuccessCheck("Should format without error", &ec, TRUE)) {
const UFormattedValue* fv = unumrf_resultAsValue(uresult, &ec);
assertSuccess("Should convert without error", &ec);
- static const UFieldPosition expectedFieldPositions[] = {
- // field, begin index, end index
- {UNUM_INTEGER_FIELD, 0, 2},
- {UNUM_COMPACT_FIELD, 2, 3},
- {UNUM_INTEGER_FIELD, 6, 9},
- {UNUM_COMPACT_FIELD, 9, 10}};
- checkFormattedValue(
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 3},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 2},
+ {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 2, 3},
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 6, 10},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 6, 9},
+ {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 9, 10}};
+ checkMixedFormattedValue(
"FormattedNumber as FormattedValue",
fv,
u"55K – 150K",
- UFIELD_CATEGORY_NUMBER,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
3000,
5000,
expectedString);
- static const UFieldPosition expectedFieldPositions[] = {
- // field, begin index, end index
- {UNUM_INTEGER_FIELD, 0, 1},
- {UNUM_COMPACT_FIELD, 1, 2},
- {UNUM_INTEGER_FIELD, 5, 6},
- {UNUM_COMPACT_FIELD, 6, 7},
- {UNUM_MEASURE_UNIT_FIELD, 8, 9}};
- checkFormattedValue(
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 2},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 1},
+ {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 1, 2},
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 5, 7},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 5, 6},
+ {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 6, 7},
+ {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD, 8, 9}};
+ checkMixedFormattedValue(
message,
result,
expectedString,
- UFIELD_CATEGORY_NUMBER,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
87654321,
98765432,
expectedString);
- static const UFieldPosition expectedFieldPositions[] = {
- // field, begin index, end index
- {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
- {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
- {UNUM_INTEGER_FIELD, 0, 10},
- {UNUM_GROUPING_SEPARATOR_FIELD, 13, 14},
- {UNUM_GROUPING_SEPARATOR_FIELD, 17, 18},
- {UNUM_INTEGER_FIELD, 11, 21}};
- checkFormattedValue(
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 10},
+ {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
+ {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 10},
+ {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 11, 21},
+ {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 13, 14},
+ {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 17, 18},
+ {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 11, 21}};
+ checkMixedFormattedValue(
message,
result,
expectedString,
- UFIELD_CATEGORY_NUMBER,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
*/
public class FormattedStringBuilder implements CharSequence, Appendable {
+ public static interface FieldWrapper {
+ java.text.Format.Field unwrap();
+ }
+
+ public static java.text.Format.Field unwrapField(Object field) {
+ if (field == null) {
+ return null;
+ } else if (field instanceof FieldWrapper) {
+ return ((FieldWrapper) field).unwrap();
+ } else if (field instanceof java.text.Format.Field) {
+ return (java.text.Format.Field) field;
+ } else {
+ throw new AssertionError("Not a field: " + field);
+ }
+ }
+
/** A constant, empty FormattedStringBuilder. Do NOT call mutative operations on this. */
public static final FormattedStringBuilder EMPTY = new FormattedStringBuilder();
if (fields.length != length)
return false;
for (int i = 0; i < length; i++) {
- if (this.chars[zero + i] != chars[i])
+ if (this.chars[zero + i] != chars[i]) {
return false;
- if (this.fields[zero + i] != fields[i])
+ }
+ if (unwrapField(this.fields[zero + i]) != unwrapField(fields[i])) {
return false;
+ }
}
return true;
}
if (length != other.length)
return false;
for (int i = 0; i < length; i++) {
- if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) {
+ if (charAt(i) != other.charAt(i)) {
+ return false;
+ }
+ if (unwrapField(fieldAt(i)) != unwrapField(other.fieldAt(i))) {
return false;
}
}
* Placeholder field used for calculating spans.
* Does not currently support nested fields beyond one level.
*/
- public static class SpanFieldPlaceholder {
+ public static class SpanFieldPlaceholder implements FormattedStringBuilder.FieldWrapper {
public UFormat.SpanField spanField;
public Field normalField;
public Object value;
+ public int start;
public int length;
+
+ public Field unwrap() {
+ return normalField;
+ }
}
/**
return -1;
}
+ /**
+ * Upgrade a range of a string to a span field.
+ *
+ * Similar to appendSpanInfo in ICU4C.
+ */
+ public static void applySpanRange(
+ FormattedStringBuilder self,
+ UFormat.SpanField spanField,
+ Object value,
+ int start,
+ int end) {
+ for (int i = start + self.zero; i < end + self.zero; i++) {
+ Object oldField = self.fields[i];
+ SpanFieldPlaceholder newField = new SpanFieldPlaceholder();
+ newField.spanField = spanField;
+ newField.normalField = (java.text.Format.Field) oldField;
+ newField.value = value;
+ newField.start = start;
+ newField.length = end - start;
+ self.fields[i] = newField;
+ }
+ }
+
public static boolean nextFieldPosition(FormattedStringBuilder self, FieldPosition fp) {
java.text.Format.Field rawField = fp.getFieldAttribute();
public static boolean nextPosition(FormattedStringBuilder self, ConstrainedFieldPosition cfpos, Field numericField) {
int fieldStart = -1;
Object currField = null;
+ boolean prevIsSpan = false;
+ if (cfpos.getLimit() > 0) {
+ prevIsSpan = cfpos.getField() instanceof UFormat.SpanField
+ && cfpos.getStart() < cfpos.getLimit();
+ }
+ boolean prevIsNumeric = false;
+ if (numericField != null) {
+ prevIsNumeric = cfpos.getField() == numericField;
+ }
+ boolean prevIsInteger = cfpos.getField() == NumberFormat.Field.INTEGER;
+
for (int i = self.zero + cfpos.getLimit(); i <= self.zero + self.length; i++) {
Object _field = (i < self.zero + self.length) ? self.fields[i] : NullField.END;
// Case 1: currently scanning a field.
}
continue;
}
+ // Special case: emit normalField if we are pointing at the end of spanField.
+ if (i > self.zero && prevIsSpan) {
+ assert self.fields[i-1] instanceof SpanFieldPlaceholder;
+ SpanFieldPlaceholder ph = (SpanFieldPlaceholder) self.fields[i-1];
+ if (ph.normalField == ListFormatter.Field.ELEMENT) {
+ // Special handling for ULISTFMT_ELEMENT_FIELD
+ if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) {
+ fieldStart = i - self.zero - ph.length;
+ int end = fieldStart + ph.length;
+ cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end);
+ return true;
+ }
+ } else {
+ // Re-wind, since there may be multiple fields in the span.
+ i -= ph.length;
+ assert i >= self.zero;
+ _field = ((SpanFieldPlaceholder) self.fields[i]).normalField;
+ }
+ }
// Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
if (cfpos.matchesField(NumberFormat.Field.INTEGER, null)
&& i > self.zero
- // don't return the same field twice in a row:
- && i - self.zero > cfpos.getLimit()
+ && !prevIsInteger
+ && !prevIsNumeric
&& isIntOrGroup(self.fields[i - 1])
&& !isIntOrGroup(_field)) {
int j = i - 1;
if (numericField != null
&& cfpos.matchesField(numericField, null)
&& i > self.zero
- // don't return the same field twice in a row:
- && (i - self.zero > cfpos.getLimit() || cfpos.getField() != numericField)
+ && !prevIsNumeric
&& isNumericField(self.fields[i - 1])
&& !isNumericField(_field)) {
// Re-wind to the beginning of the field and then emit it
cfpos.setState(numericField, null, j - self.zero + 1, i - self.zero);
return true;
}
- // Special case: emit normalField if we are pointing at the end of spanField.
- if (i > self.zero
- && self.fields[i-1] instanceof SpanFieldPlaceholder) {
- SpanFieldPlaceholder ph = (SpanFieldPlaceholder) self.fields[i-1];
- if (cfpos.matchesField(ph.normalField, null)
- && (cfpos.getLimit() < i - self.zero
- || cfpos.getField() != ph.normalField)) {
- cfpos.setState(ph.normalField, null, i - self.zero - ph.length, i - self.zero);
- return true;
- }
- }
- // Special case: skip over INTEGER; will be coalesced later.
- if (_field == NumberFormat.Field.INTEGER) {
- _field = null;
- }
- // Case 2: no field starting at this position.
- if (_field == null || _field == NullField.END) {
- continue;
- }
- // Case 3: check for field starting at this position
- // Case 3a: SpanField placeholder
+ // Check for span field
+ SpanFieldPlaceholder ph = null;
if (_field instanceof SpanFieldPlaceholder) {
- SpanFieldPlaceholder ph = (SpanFieldPlaceholder) _field;
+ ph = (SpanFieldPlaceholder) _field;
+ _field = ph.normalField;
+ }
+ if (ph != null && (ph.start == -1 || ph.start == i - self.zero)) {
if (cfpos.matchesField(ph.spanField, ph.value)) {
fieldStart = i - self.zero;
int end = fieldStart + ph.length;
cfpos.setState(ph.spanField, ph.value, fieldStart, end);
return true;
- } else {
- // Failed to match; jump ahead
- i += ph.length - 1;
- continue;
+ } else if (ph.normalField == ListFormatter.Field.ELEMENT) {
+ // Special handling for ListFormatter.Field.ELEMENT
+ if (cfpos.matchesField(ListFormatter.Field.ELEMENT, null)) {
+ fieldStart = i - self.zero;
+ int end = fieldStart + ph.length;
+ cfpos.setState(ListFormatter.Field.ELEMENT, null, fieldStart, end);
+ return true;
+ } else {
+ // Failed to match; jump ahead
+ i += ph.length - 1;
+ // goto loopend
+ }
}
}
- // Case 3b: No SpanField
+ // Special case: skip over INTEGER; will be coalesced later.
+ else if (_field == NumberFormat.Field.INTEGER) {
+ _field = null;
+ }
+ // No field starting at this position.
+ else if (_field == null || _field == NullField.END) {
+ // goto loopend
+ }
+ // No SpanField
else if (cfpos.matchesField((Field) _field, null)) {
fieldStart = i - self.zero;
currField = _field;
}
+ // loopend:
+ prevIsSpan = false;
+ prevIsNumeric = false;
+ prevIsInteger = false;
}
assert currField == null;
}
private static boolean isIntOrGroup(Object field) {
+ field = FormattedStringBuilder.unwrapField(field);
return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
}
private static boolean isNumericField(Object field) {
+ field = FormattedStringBuilder.unwrapField(field);
return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass());
}
public int index3() {
return lengthPrefix + length1 + lengthInfix + length2;
}
+
+ public int index4() {
+ return lengthPrefix + length1 + lengthInfix + length2 + lengthSuffix;
+ }
}
// FormattedStringBuilder and BigDecimal are mutable, so we can't call
// #equals() or #hashCode() on them directly.
FormattedNumberRange _other = (FormattedNumberRange) other;
- return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
- && Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
+ return string.contentEquals(_other.string)
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
}
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.number;
+import java.io.InvalidObjectException;
import java.util.Locale;
import com.ibm.icu.util.ULocale;
+import com.ibm.icu.text.UFormat;
/**
* The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
NOT_EQUAL
}
+ /**
+ * Class for span fields in FormattedNumberRange.
+ *
+ * @draft ICU 69
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static final class SpanField extends UFormat.SpanField {
+ private static final long serialVersionUID = 8750397196515368729L;
+
+ /**
+ * The concrete field used for spans in FormattedNumberRange.
+ *
+ * Instances of NUMBER_RANGE_SPAN should have an associated value, the index within the input
+ * list that is represented by the span.
+ *
+ * @draft ICU 69
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static final SpanField NUMBER_RANGE_SPAN = new SpanField("number-range-span");
+
+ private SpanField(String name) {
+ super(name);
+ }
+
+ /**
+ * Serialization method resolve instances to the constant
+ * NumberRangeFormatter.SpanField values
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ @Override
+ protected Object readResolve() throws InvalidObjectException {
+ if (this.getName().equals(NUMBER_RANGE_SPAN.getName()))
+ return NUMBER_RANGE_SPAN;
+
+ throw new InvalidObjectException("An invalid object.");
+ }
+ }
+
private static final UnlocalizedNumberRangeFormatter BASE = new UnlocalizedNumberRangeFormatter();
/**
import java.util.MissingResourceException;
import com.ibm.icu.impl.FormattedStringBuilder;
+import com.ibm.icu.impl.FormattedValueStringBuilderImpl;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.PatternProps;
// TODO: Support padding?
if (collapseInner) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modInner.apply(string, h.index2(), h.index4());
}
if (collapseMiddle) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index4());
}
if (collapseOuter) {
- // Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
- h.lengthInfix += mod.apply(string, h.index0(), h.index3());
+ h.lengthSuffix += mod.apply(string, h.index0(), h.index4());
+ h.lengthPrefix += mod.getPrefixLength();
+ h.lengthSuffix -= mod.getPrefixLength();
} else {
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
- h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
+ h.length2 += micros2.modOuter.apply(string, h.index2(), h.index4());
}
+
+ // Now that all pieces are added, save the span info.
+ FormattedValueStringBuilderImpl.applySpanRange(
+ string,
+ NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN,
+ 0,
+ h.index0(),
+ h.index1());
+ FormattedValueStringBuilderImpl.applySpanRange(
+ string,
+ NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN,
+ 1,
+ h.index2(),
+ h.index3());
}
Modifier resolveModifierPlurals(Modifier first, Modifier second) {
field.spanField = SpanField.LIST_SPAN;
field.normalField = Field.ELEMENT;
field.value = position;
+ field.start = -1;
field.length = elementString.length();
string.append(elementString, field);
} else {
5000,
expectedString);
Object[][] expectedFieldPositions = new Object[][]{
+ {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN, 0, 2, 0},
{NumberFormat.Field.INTEGER, 0, 1},
{NumberFormat.Field.COMPACT, 1, 2},
+ {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN, 5, 7, 1},
{NumberFormat.Field.INTEGER, 5, 6},
{NumberFormat.Field.COMPACT, 6, 7},
{NumberFormat.Field.MEASURE_UNIT, 8, 9}};
98765432,
expectedString);
Object[][] expectedFieldPositions = new Object[][]{
+ {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN, 0, 10, 0},
{NumberFormat.Field.GROUPING_SEPARATOR, 2, 3},
{NumberFormat.Field.GROUPING_SEPARATOR, 6, 7},
{NumberFormat.Field.INTEGER, 0, 10},
+ {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN, 11, 21, 1},
{NumberFormat.Field.GROUPING_SEPARATOR, 13, 14},
{NumberFormat.Field.GROUPING_SEPARATOR, 17, 18},
{NumberFormat.Field.INTEGER, 11, 21}};
import com.ibm.icu.impl.TZDBTimeZoneNames;
import com.ibm.icu.impl.TimeZoneGenericNames;
import com.ibm.icu.impl.TimeZoneGenericNames.GenericNameType;
+import com.ibm.icu.number.NumberRangeFormatter;
import com.ibm.icu.text.ChineseDateFormat;
import com.ibm.icu.text.ChineseDateFormatSymbols;
import com.ibm.icu.text.CompactDecimalFormat;
}
}
+ public static class NumberRangeFormatterSpanFieldHandler implements SerializableTestUtility.Handler
+ {
+ @Override
+ public Object[] getTestObjects()
+ {
+ return new Object[] {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN};
+ }
+
+ @Override
+ public boolean hasSameBehavior(Object a, Object b)
+ {
+ return (a == b);
+ }
+ }
+
public static class DateFormatHandler implements SerializableTestUtility.Handler
{
static HashMap cannedPatterns = new HashMap();
map.put("com.ibm.icu.text.DateIntervalFormat$SpanField", new FormatHandler.DateIntervalSpanFieldHandler());
map.put("com.ibm.icu.text.ListFormatter$Field", new FormatHandler.ListFormatterFieldHandler());
map.put("com.ibm.icu.text.ListFormatter$SpanField", new FormatHandler.ListFormatterSpanFieldHandler());
+ map.put("com.ibm.icu.number.NumberRangeFormatter$SpanField", new FormatHandler.NumberRangeFormatterSpanFieldHandler());
map.put("com.ibm.icu.impl.duration.BasicDurationFormat", new FormatHandler.BasicDurationFormatHandler());
map.put("com.ibm.icu.impl.RelativeDateFormat", new FormatHandler.RelativeDateFormatHandler());