}
UErrorCode localStatus = U_ZERO_ERROR;
FormattedNumber output = fields->formatter->formatDouble(number, localStatus);
- output.populateFieldPosition(pos, localStatus);
+ fieldPositionHelper(output, pos, appendTo.length(), localStatus);
auto appendable = UnicodeStringAppendable(appendTo);
output.appendTo(appendable);
return appendTo;
return appendTo;
}
FormattedNumber output = fields->formatter->formatDouble(number, status);
- output.populateFieldPosition(pos, status);
+ fieldPositionHelper(output, pos, appendTo.length(), status);
auto appendable = UnicodeStringAppendable(appendTo);
output.appendTo(appendable);
return appendTo;
}
UErrorCode localStatus = U_ZERO_ERROR;
FormattedNumber output = fields->formatter->formatInt(number, localStatus);
- output.populateFieldPosition(pos, localStatus);
+ fieldPositionHelper(output, pos, appendTo.length(), localStatus);
auto appendable = UnicodeStringAppendable(appendTo);
output.appendTo(appendable);
return appendTo;
return appendTo;
}
FormattedNumber output = fields->formatter->formatInt(number, status);
- output.populateFieldPosition(pos, status);
+ fieldPositionHelper(output, pos, appendTo.length(), status);
auto appendable = UnicodeStringAppendable(appendTo);
output.appendTo(appendable);
return appendTo;
DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos,
UErrorCode& status) const {
FormattedNumber output = fields->formatter->formatDecimalQuantity(number, status);
- output.populateFieldPosition(pos, status);
+ fieldPositionHelper(output, pos, appendTo.length(), status);
auto appendable = UnicodeStringAppendable(appendTo);
output.appendTo(appendable);
return appendTo;
}
}
+void
+DecimalFormat::fieldPositionHelper(const number::FormattedNumber& formatted, FieldPosition& fieldPosition,
+ int32_t offset, UErrorCode& status) {
+ fieldPosition.setEndIndex(0); // always return first occurrence
+ bool found = formatted.nextFieldPosition(fieldPosition, status);
+ if (found && offset != 0) {
+ fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + offset);
+ fieldPosition.setEndIndex(fieldPosition.getEndIndex() + offset);
+ }
+}
+
// To debug fast-format, change void(x) to printf(x)
#define trace(x) void(x)
+++ /dev/null
-// © 2018 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-
-#include "unicode/utypes.h"
-
-#if !UCONFIG_NO_FORMATTING
-#ifndef __SOURCE_FIELDPOSUTIL_H__
-#define __SOURCE_FIELDPOSUTIL_H__
-
-U_NAMESPACE_BEGIN
-
-/**
- * Wraps a UFieldPosition and makes it usable as a FieldPosition. Example:
- *
- * <pre>
- * UFieldPositionWrapper wrapper(myUFPos);
- * u_favorite_function_taking_ufpos(wrapper);
- * // when destructed, the wrapper saves the data back into myUFPos
- * </pre>
- */
-class UFieldPositionWrapper : public UMemory {
- public:
- explicit UFieldPositionWrapper(UFieldPosition& ufpos)
- : _ufpos(ufpos) {
- _fpos.setField(_ufpos.field);
- _fpos.setBeginIndex(_ufpos.beginIndex);
- _fpos.setEndIndex(_ufpos.endIndex);
- }
-
- /** When destructed, copies the information from the fpos into the ufpos. */
- ~UFieldPositionWrapper() {
- _ufpos.field = _fpos.getField();
- _ufpos.beginIndex = _fpos.getBeginIndex();
- _ufpos.endIndex = _fpos.getEndIndex();
- }
-
- /** Conversion operator to FieldPosition */
- operator FieldPosition&() {
- return _fpos;
- }
-
- private:
- FieldPosition _fpos;
- UFieldPosition& _ufpos;
-};
-
-U_NAMESPACE_END
-
-#endif //__SOURCE_FIELDPOSUTIL_H__
-#endif /* #if !UCONFIG_NO_FORMATTING */
<ClInclude Include="number_multiplier.h" />
<ClInclude Include="number_currencysymbols.h" />
<ClInclude Include="number_skeletons.h" />
- <ClInclude Include="fieldposutil.h" />
<ClInclude Include="numparse_stringsegment.h" />
<ClInclude Include="numparse_impl.h" />
<ClInclude Include="numparse_symbols.h" />
<ClInclude Include="number_skeletons.h">
<Filter>formatting</Filter>
</ClInclude>
- <ClInclude Include="fieldposutil.h">
- <Filter>formatting</Filter>
- </ClInclude>
<ClInclude Include="numparse_stringsegment.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="number_multiplier.h" />
<ClInclude Include="number_currencysymbols.h" />
<ClInclude Include="number_skeletons.h" />
- <ClInclude Include="fieldposutil.h" />
<ClInclude Include="numparse_stringsegment.h" />
<ClInclude Include="numparse_impl.h" />
<ClInclude Include="numparse_symbols.h" />
#include "number_utypes.h"
#include "unicode/numberformatter.h"
#include "unicode/unumberformatter.h"
-#include "fieldposutil.h"
using namespace icu;
using namespace icu::number;
return result->string.toTempUnicodeString().extract(buffer, bufferCapacity, *ec);
}
-U_CAPI void U_EXPORT2
-unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec) {
+U_CAPI UBool U_EXPORT2
+unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec) {
if (ufpos == nullptr) {
*ec = U_ILLEGAL_ARGUMENT_ERROR;
- return;
+ return FALSE;
}
const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec);
- if (U_FAILURE(*ec)) { return; }
+ if (U_FAILURE(*ec)) { return FALSE; }
- UFieldPositionWrapper helper(*ufpos);
- result->string.populateFieldPosition(helper, 0, *ec);
+ FieldPosition fp;
+ fp.setField(ufpos->field);
+ fp.setBeginIndex(ufpos->beginIndex);
+ fp.setEndIndex(ufpos->endIndex);
+ bool retval = result->string.nextFieldPosition(fp, *ec);
+ ufpos->beginIndex = fp.getBeginIndex();
+ ufpos->endIndex = fp.getEndIndex();
+ // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
+ return retval ? TRUE : FALSE;
}
U_CAPI void U_EXPORT2
-unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer,
- UErrorCode* ec) {
+unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer,
+ UErrorCode* ec) {
if (ufpositer == nullptr) {
*ec = U_ILLEGAL_ARGUMENT_ERROR;
return;
if (U_FAILURE(*ec)) { return; }
auto* helper = reinterpret_cast<FieldPositionIterator*>(ufpositer);
- result->string.populateFieldPositionIterator(*helper, *ec);
+ result->string.getAllFieldPositions(*helper, *ec);
}
U_CAPI void U_EXPORT2
status = fErrorCode;
return;
}
- fResults->string.populateFieldPosition(fieldPosition, 0, status);
+ // in case any users were depending on the old behavior:
+ fieldPosition.setEndIndex(0);
+ fResults->string.nextFieldPosition(fieldPosition, status);
+}
+
+UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return FALSE;
+ }
+ if (fResults == nullptr) {
+ status = fErrorCode;
+ return FALSE;
+ }
+ // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
+ return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE;
}
void FormattedNumber::populateFieldPositionIterator(FieldPositionIterator& iterator, UErrorCode& status) {
status = fErrorCode;
return;
}
- fResults->string.populateFieldPositionIterator(iterator, status);
+ fResults->string.getAllFieldPositions(iterator, status);
+}
+
+void FormattedNumber::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if (fResults == nullptr) {
+ status = fErrorCode;
+ return;
+ }
+ fResults->string.getAllFieldPositions(iterator, status);
}
void FormattedNumber::getDecimalQuantity(DecimalQuantity& output, UErrorCode& status) const {
return true;
}
-void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offset, UErrorCode &status) const {
+bool NumberStringBuilder::nextFieldPosition(FieldPosition& fp, UErrorCode& status) const {
int32_t rawField = fp.getField();
if (rawField == FieldPosition::DONT_CARE) {
- return;
+ return FALSE;
}
if (rawField < 0 || rawField >= UNUM_FIELD_COUNT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
- return;
+ return FALSE;
}
auto field = static_cast<Field>(rawField);
bool seenStart = false;
int32_t fractionStart = -1;
- for (int i = fZero; i <= fZero + fLength; i++) {
+ int32_t startIndex = fp.getEndIndex();
+ for (int i = fZero + startIndex; i <= fZero + fLength; i++) {
Field _field = UNUM_FIELD_COUNT;
if (i < fZero + fLength) {
_field = getFieldPtr()[i];
if (field == UNUM_INTEGER_FIELD && _field == UNUM_GROUPING_SEPARATOR_FIELD) {
continue;
}
- fp.setEndIndex(i - fZero + offset);
+ fp.setEndIndex(i - fZero);
break;
} else if (!seenStart && field == _field) {
- fp.setBeginIndex(i - fZero + offset);
+ fp.setBeginIndex(i - fZero);
seenStart = true;
}
if (_field == UNUM_INTEGER_FIELD || _field == UNUM_DECIMAL_SEPARATOR_FIELD) {
}
}
- // Backwards compatibility: FRACTION needs to start after INTEGER if empty
- if (field == UNUM_FRACTION_FIELD && !seenStart) {
- fp.setBeginIndex(fractionStart + offset);
- fp.setEndIndex(fractionStart + offset);
+ // Backwards compatibility: FRACTION needs to start after INTEGER if empty.
+ // Do not return that a field was found, though, since there is not actually a fraction part.
+ if (field == UNUM_FRACTION_FIELD && !seenStart && fractionStart != -1) {
+ fp.setBeginIndex(fractionStart);
+ fp.setEndIndex(fractionStart);
}
+
+ return seenStart;
}
-void NumberStringBuilder::populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const {
+void NumberStringBuilder::getAllFieldPositions(FieldPositionIterator& fpi, UErrorCode& status) const {
// TODO: Set an initial capacity on uvec?
LocalPointer <UVector32> uvec(new UVector32(status), status);
if (U_FAILURE(status)) {
bool contentEquals(const NumberStringBuilder &other) const;
- void populateFieldPosition(FieldPosition &fp, int32_t offset, UErrorCode &status) const;
+ bool nextFieldPosition(FieldPosition& fp, UErrorCode& status) const;
- void populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const;
+ void getAllFieldPositions(FieldPositionIterator& fpi, UErrorCode& status) const;
private:
bool fUsingHeap = false;
namespace number {
class LocalizedNumberFormatter;
+class FormattedNumber;
namespace impl {
class DecimalQuantity;
struct DecimalFormatFields;
const numparse::impl::NumberParserImpl* getCurrencyParser(UErrorCode& status) const;
+ static void fieldPositionHelper(const number::FormattedNumber& formatted, FieldPosition& fieldPosition,
+ int32_t offset, UErrorCode& status);
+
void setupFastFormat();
bool fastFormatDouble(double input, UnicodeString& output) const;
* The FieldPosition to populate with the start and end indices of the desired field.
* @param status
* Set if an error occurs while populating the FieldPosition.
- * @draft ICU 60
+ * @deprecated ICU 62 Use {@link #toCharacterIterator} instead. This method will be removed in a future
+ * release. See http://bugs.icu-project.org/trac/ticket/13746
* @see UNumberFormatFields
*/
void populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status);
+ /**
+ * Determines the start and end indices of the next occurrence of the given <em>field</em> in the
+ * output string. This allows you to determine the locations of, for example, the integer part,
+ * fraction part, or symbols.
+ *
+ * If a field occurs just once, calling this method will find that occurrence and return it. If a
+ * field occurs multiple times, this method may be called repeatedly with the following pattern:
+ *
+ * <pre>
+ * FieldPosition fpos(UNUM_GROUPING_SEPARATOR_FIELD);
+ * while (formattedNumber.nextFieldPosition(fpos, status)) {
+ * // do something with fpos.
+ * }
+ * </pre>
+ *
+ * This method is useful if you know which field to query. If you want all available field position
+ * information, use #getAllFields().
+ *
+ * @param fieldPosition
+ * Input+output variable. On input, the "field" property determines which field to look up,
+ * and the "endIndex" property determines where to begin the search. On output, the
+ * "beginIndex" field is set to the beginning of the first occurrence of the field after the
+ * input "endIndex", and "endIndex" is set to the end of that occurrence of the field
+ * (exclusive index). If a field position is not found, the FieldPosition is not changed and
+ * the method returns FALSE.
+ * @param status
+ * Set if an error occurs while populating the FieldPosition.
+ * @return TRUE if a new occurrence of the field was found; FALSE otherwise.
+ * @draft ICU 62
+ * @see UNumberFormatFields
+ */
+ UBool nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const;
+
/**
* Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in
* the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and sign.
* The FieldPositionIterator to populate with all of the fields present in the formatted number.
* @param status
* Set if an error occurs while populating the FieldPositionIterator.
- * @draft ICU 60
+ * @deprecated ICU 62 Use {@link #getAllFieldPositions} instead. This method will be removed in a
+ * future release. See http://bugs.icu-project.org/trac/ticket/13746
* @see UNumberFormatFields
*/
void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status);
+ /**
+ * Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in
+ * the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and sign.
+ *
+ * If information on only one field is needed, use #nextFieldPosition().
+ *
+ * @param iterator
+ * The FieldPositionIterator to populate with all of the fields present in the formatted number.
+ * @param status
+ * Set if an error occurs while populating the FieldPositionIterator.
+ * @draft ICU 62
+ * @see UNumberFormatFields
+ */
+ void getAllFieldPositions(FieldPositionIterator &iterator, UErrorCode &status) const;
+
#ifndef U_HIDE_INTERNAL_API
/**
/**
- * Determines the start and end indices of the first occurrence of the given field in the output string.
- * This allows you to determine the locations of the integer part, fraction part, and sign.
+ * Determines the start and end indices of the next occurrence of the given <em>field</em> in the
+ * output string. This allows you to determine the locations of, for example, the integer part,
+ * fraction part, or symbols.
*
- * If a field occurs multiple times in an output string, such as a grouping separator, this method will
- * only ever return the first occurrence. Use unumf_resultGetAllFields() to access all occurrences of an
- * attribute.
+ * If a field occurs just once, calling this method will find that occurrence and return it. If a
+ * field occurs multiple times, this method may be called repeatedly with the following pattern:
*
- * @param uresult The object containing the formatted number.
- * @param fpos
- * A pointer to a UFieldPosition. On input, position->field is read. On output,
- * position->beginIndex and position->endIndex indicate the beginning and ending indices of field
- * number position->field, if such a field exists.
+ * <pre>
+ * UFieldPosition ufpos = {UNUM_GROUPING_SEPARATOR_FIELD, 0, 0};
+ * while (unumf_resultNextFieldPosition(uresult, ufpos, &ec)) {
+ * // do something with ufpos.
+ * }
+ * </pre>
+ *
+ * This method is useful if you know which field to query. If you want all available field position
+ * information, use unumf_resultGetAllFieldPositions().
+ *
+ * NOTE: All fields of the UFieldPosition must be initialized before calling this method.
+ *
+ * @param fieldPosition
+ * Input+output variable. On input, the "field" property determines which field to look up,
+ * and the "endIndex" property determines where to begin the search. On output, the
+ * "beginIndex" field is set to the beginning of the first occurrence of the field after the
+ * input "endIndex", and "endIndex" is set to the end of that occurrence of the field
+ * (exclusive index). If a field position is not found, the FieldPosition is not changed and
+ * the method returns FALSE.
* @param ec Set if an error occurs.
* @draft ICU 62
*/
-U_DRAFT void U_EXPORT2
-unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec);
+U_DRAFT UBool U_EXPORT2
+unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec);
/**
* Populates the given iterator with all fields in the formatted output string. This allows you to
* determine the locations of the integer part, fraction part, and sign.
*
- * If you need information on only one field, consider using unumf_resultGetField().
+ * If you need information on only one field, use unumf_resultNextFieldPosition().
*
* @param uresult The object containing the formatted number.
* @param fpositer
* @draft ICU 62
*/
U_DRAFT void U_EXPORT2
-unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer,
- UErrorCode* ec);
+unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer,
+ UErrorCode* ec);
/**
// field position test:
UFieldPosition ufpos = {UNUM_DECIMAL_SEPARATOR_FIELD};
- unumf_resultGetField(uresult, &ufpos, &ec);
+ unumf_resultNextFieldPosition(uresult, &ufpos, &ec);
assertIntEquals("Field position should be correct", 14, ufpos.beginIndex);
assertIntEquals("Field position should be correct", 15, ufpos.endIndex);
// field position iterator test:
UFieldPositionIterator* ufpositer = ufieldpositer_open(&ec);
assertSuccess("Should create iterator without error", &ec);
- unumf_resultGetAllFields(uresult, ufpositer, &ec);
+ unumf_resultGetAllFieldPositions(uresult, ufpositer, &ec);
static const UFieldPosition expectedFields[] = {
// Field, begin index, end index
{UNUM_SIGN_FIELD, 0, 1},
actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex);
assertTrue("No more fields; should return a negative index", actual.field < 0);
+ // next field iteration:
+ actual.field = UNUM_GROUPING_SEPARATOR_FIELD;
+ actual.beginIndex = 0;
+ actual.endIndex = 0;
+ int32_t i = 1;
+ while (unumf_resultNextFieldPosition(uresult, &actual, &ec)) {
+ UFieldPosition expected = expectedFields[i++];
+ assertIntEquals("Grouping separator begin index", expected.beginIndex, actual.beginIndex);
+ assertIntEquals("Grouping separator end index", expected.endIndex, actual.endIndex);
+ }
+ assertIntEquals("Should have seen all grouping separators", 4, i);
+
// cleanup:
unumf_closeResult(uresult);
unumf_close(uformatter);
void scale();
void locale();
void formatTypes();
+ void fieldPosition();
void errors();
void validRanges();
void copyMove();
TESTCASE_AUTO(scale);
TESTCASE_AUTO(locale);
TESTCASE_AUTO(formatTypes);
+ TESTCASE_AUTO(fieldPosition);
TESTCASE_AUTO(errors);
TESTCASE_AUTO(validRanges);
TESTCASE_AUTO(copyMove);
assertEquals("Format decNumber to 40 digits", str, actual);
}
+void NumberFormatterApiTest::fieldPosition() {
+ IcuTestErrorCode status(*this, "fieldPosition");
+ FormattedNumber fmtd = NumberFormatter::withLocale("en").formatDouble(-9876543210.12, status);
+ assertEquals("Should have expected format output", u"-9,876,543,210.12", fmtd.toString(status));
+
+ static const UFieldPosition expectedFieldPositions[] = {
+ // field, begin index, end index
+ {UNUM_SIGN_FIELD, 0, 1},
+ {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
+ {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
+ {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11},
+ {UNUM_INTEGER_FIELD, 1, 14},
+ {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15},
+ {UNUM_FRACTION_FIELD, 15, 17}};
+
+ FieldPositionIterator fpi;
+ fmtd.getAllFieldPositions(fpi, status);
+ int32_t i = 0;
+ FieldPosition actual;
+ while (fpi.next(actual)) {
+ UFieldPosition expected = expectedFieldPositions[i++];
+ assertEquals(
+ UnicodeString(u"Field, case #") + Int64ToUnicodeString(i),
+ expected.field,
+ actual.getField());
+ assertEquals(
+ UnicodeString(u"Iterator, begin index, case #") + Int64ToUnicodeString(i),
+ expected.beginIndex,
+ actual.getBeginIndex());
+ assertEquals(
+ UnicodeString(u"Iterator, end index, case #") + Int64ToUnicodeString(i),
+ expected.endIndex,
+ actual.getEndIndex());
+
+ // Check for the first location of the field
+ if (expected.field != UNUM_GROUPING_SEPARATOR_FIELD) {
+ FieldPosition actual2(expected.field);
+ UBool found = fmtd.nextFieldPosition(actual2, status);
+ assertEquals(
+ UnicodeString(u"Next, found first time, case #") + Int64ToUnicodeString(i),
+ (UBool) TRUE,
+ found);
+ assertEquals(
+ UnicodeString(u"Next, begin index, case #") + Int64ToUnicodeString(i),
+ expected.beginIndex,
+ actual2.getBeginIndex());
+ assertEquals(
+ UnicodeString(u"Next, end index, case #") + Int64ToUnicodeString(i),
+ expected.endIndex,
+ actual2.getEndIndex());
+ found = fmtd.nextFieldPosition(actual2, status);
+ assertEquals(
+ UnicodeString(u"Next, found second time, case #") + Int64ToUnicodeString(i),
+ (UBool) FALSE,
+ found);
+ }
+ }
+ assertEquals(
+ "Should have seen every field position",
+ sizeof(expectedFieldPositions) / sizeof(*expectedFieldPositions),
+ i);
+
+ // Test the iteration functionality of nextFieldPosition
+ actual = {UNUM_GROUPING_SEPARATOR_FIELD};
+ i = 1;
+ while (fmtd.nextFieldPosition(actual, status)) {
+ UFieldPosition expected = expectedFieldPositions[i++];
+ assertEquals(
+ UnicodeString(u"Next for grouping, field, case #") + Int64ToUnicodeString(i),
+ expected.field,
+ actual.getField());
+ assertEquals(
+ UnicodeString(u"Next for grouping, begin index, case #") + Int64ToUnicodeString(i),
+ expected.beginIndex,
+ actual.getBeginIndex());
+ assertEquals(
+ UnicodeString(u"Next for grouping, end index, case #") + Int64ToUnicodeString(i),
+ expected.endIndex,
+ actual.getEndIndex());
+ }
+ assertEquals(u"Should have seen all grouping separators", 4, i);
+
+ // Make sure strings without fraction do not contain fraction field
+ actual = {UNUM_FRACTION_FIELD};
+ fmtd = NumberFormatter::withLocale("en").formatInt(5, status);
+ assertFalse(u"No fraction part in an integer", fmtd.nextFieldPosition(actual, status));
+}
+
void NumberFormatterApiTest::errors() {
LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).rounding(
Rounder::fixedFraction(
// Get the location of the percent sign:
UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0};
- unumf_resultGetField(uresult.getAlias(), &ufpos, &ec);
+ unumf_resultNextFieldPosition(uresult.getAlias(), &ufpos, &ec);
assertEquals("Percent sign location within '0.00987%'", 7, ufpos.beginIndex);
assertEquals("Percent sign location within '0.00987%'", 8, ufpos.endIndex);
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
// Let NumberFormatTest also take care of FieldPositionIterator material.
FieldPosition fp(UNUM_CURRENCY_FIELD);
- sb.populateFieldPosition(fp, 0, status);
+ sb.nextFieldPosition(fp, status);
assertSuccess("Populating the FieldPosition", status);
assertEquals("Currency start position", str.length(), fp.getBeginIndex());
assertEquals("Currency end position", str.length() * 2, fp.getEndIndex());
*
* @param fp
* The FieldPosition to populate.
- * @param offset
- * An offset to add to the field position index; can be zero.
+ * @return true if the field was found; false if it was not found.
*/
- public void populateFieldPosition(FieldPosition fp, int offset) {
+ public boolean nextFieldPosition(FieldPosition fp) {
java.text.Format.Field rawField = fp.getFieldAttribute();
if (rawField == null) {
rawField = NumberFormat.Field.FRACTION;
} else {
// No field is set
- return;
+ return false;
}
}
- if (!(rawField instanceof com.ibm.icu.text.NumberFormat.Field)) {
+ if (!(rawField instanceof NumberFormat.Field)) {
throw new IllegalArgumentException(
"You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: "
+ rawField.getClass().toString());
}
- /* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
+ NumberFormat.Field field = (NumberFormat.Field) rawField;
boolean seenStart = false;
int fractionStart = -1;
- for (int i = zero; i <= zero + length; i++) {
+ int startIndex = fp.getEndIndex();
+ for (int i = zero + startIndex; i <= zero + length; i++) {
Field _field = (i < zero + length) ? fields[i] : null;
if (seenStart && field != _field) {
// Special case: GROUPING_SEPARATOR counts as an INTEGER.
&& _field == NumberFormat.Field.GROUPING_SEPARATOR) {
continue;
}
- fp.setEndIndex(i - zero + offset);
+ fp.setEndIndex(i - zero);
break;
} else if (!seenStart && field == _field) {
- fp.setBeginIndex(i - zero + offset);
+ fp.setBeginIndex(i - zero);
seenStart = true;
}
if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.Field.DECIMAL_SEPARATOR) {
}
}
- // Backwards compatibility: FRACTION needs to start after INTEGER if empty
- if (field == NumberFormat.Field.FRACTION && !seenStart) {
- fp.setBeginIndex(fractionStart + offset);
- fp.setEndIndex(fractionStart + offset);
+ // Backwards compatibility: FRACTION needs to start after INTEGER if empty.
+ // Do not return that a field was found, though, since there is not actually a fraction part.
+ if (field == NumberFormat.Field.FRACTION && !seenStart && fractionStart != -1) {
+ fp.setBeginIndex(fractionStart);
+ fp.setEndIndex(fractionStart);
}
+
+ return seenStart;
}
- public AttributedCharacterIterator getIterator() {
+ public AttributedCharacterIterator toCharacterIterator() {
AttributedString as = new AttributedString(toString());
Field current = null;
int currentStart = -1;
*
* @param fieldPosition
* The FieldPosition to populate with the start and end indices of the desired field.
- * @draft ICU 60
- * @provisional This API might change or be removed in a future release.
+ * @deprecated ICU 62 Use {@link #nextFieldPosition} instead. This method will be removed in a future
+ * release. See http://bugs.icu-project.org/trac/ticket/13746
* @see com.ibm.icu.text.NumberFormat.Field
* @see NumberFormatter
*/
+ @Deprecated
public void populateFieldPosition(FieldPosition fieldPosition) {
- populateFieldPosition(fieldPosition, 0);
+ // in case any users were depending on the old behavior:
+ fieldPosition.setEndIndex(0);
+ nextFieldPosition(fieldPosition);
}
/**
- * @internal
- * @deprecated This API is ICU internal only.
+ * Determines the start and end indices of the next occurrence of the given <em>field</em> in the
+ * output string. This allows you to determine the locations of, for example, the integer part,
+ * fraction part, or symbols.
+ * <p>
+ * If a field occurs just once, calling this method will find that occurrence and return it. If a
+ * field occurs multiple times, this method may be called repeatedly with the following pattern:
+ * <p>
+ * <pre>
+ * FieldPosition fpos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
+ * while (formattedNumber.nextFieldPosition(fpos, status)) {
+ * // do something with fpos.
+ * }
+ * </pre>
+ * <p>
+ * This method is useful if you know which field to query. If you want all available field position
+ * information, use #getAllFields().
+ *
+ * @param fieldPosition
+ * Input+output variable. On input, the "field" property determines which field to look up,
+ * and the "endIndex" property determines where to begin the search. On output, the
+ * "beginIndex" field is set to the beginning of the first occurrence of the field after the
+ * input "endIndex", and "endIndex" is set to the end of that occurrence of the field
+ * (exclusive index). If a field position is not found, the FieldPosition is not changed and
+ * the method returns false.
+ * @return true if a new occurrence of the field was found; false otherwise.
+ * @draft ICU 62
+ * @provisional This API might change or be removed in a future release.
+ * @see com.ibm.icu.text.NumberFormat.Field
+ * @see NumberFormatter
*/
- @Deprecated
- public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
- nsb.populateFieldPosition(fieldPosition, offset);
+ public boolean nextFieldPosition(FieldPosition fieldPosition) {
fq.populateUFieldPosition(fieldPosition);
+ return nsb.nextFieldPosition(fieldPosition);
}
/**
* Export the formatted number as an AttributedCharacterIterator. This allows you to determine which
* characters in the output string correspond to which <em>fields</em>, such as the integer part,
* fraction part, and sign.
+ * <p>
+ * If information on only one field is needed, consider using populateFieldPosition() instead.
*
+ * @return An AttributedCharacterIterator, containing information on the field attributes of the
+ * number string.
+ * @deprecated ICU 62 Use {@link #toCharacterIterator} instead. This method will be removed in a future
+ * release. See http://bugs.icu-project.org/trac/ticket/13746
+ * @see com.ibm.icu.text.NumberFormat.Field
+ * @see AttributedCharacterIterator
+ * @see NumberFormatter
+ */
+ @Deprecated
+ public AttributedCharacterIterator getFieldIterator() {
+ return nsb.toCharacterIterator();
+ }
+
+ /**
+ * Export the formatted number as an AttributedCharacterIterator. This allows you to determine which
+ * characters in the output string correspond to which <em>fields</em>, such as the integer part,
+ * fraction part, and sign.
* <p>
* If information on only one field is needed, consider using populateFieldPosition() instead.
*
* @return An AttributedCharacterIterator, containing information on the field attributes of the
* number string.
- * @draft ICU 60
+ * @draft ICU 62
* @provisional This API might change or be removed in a future release.
* @see com.ibm.icu.text.NumberFormat.Field
* @see AttributedCharacterIterator
* @see NumberFormatter
*/
- public AttributedCharacterIterator getFieldIterator() {
- return nsb.getIterator();
+ public AttributedCharacterIterator toCharacterIterator() {
+ return nsb.toCharacterIterator();
}
/**
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(number);
- output.populateFieldPosition(fieldPosition, result.length());
+ fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
}
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(number);
- output.populateFieldPosition(fieldPosition, result.length());
+ fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
}
@Override
public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(number);
- output.populateFieldPosition(fieldPosition, result.length());
+ fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
}
public StringBuffer format(
java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(number);
- output.populateFieldPosition(fieldPosition, result.length());
+ fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
}
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(number);
- output.populateFieldPosition(fieldPosition, result.length());
+ fieldPositionHelper(output, fieldPosition, result.length());
output.appendTo(result);
return result;
}
* @stable ICU 3.0
*/
@Override
- public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(CurrencyAmount currAmt, StringBuffer result, FieldPosition fieldPosition) {
FormattedNumber output = formatter.format(currAmt);
- output.populateFieldPosition(pos, toAppendTo.length());
- output.appendTo(toAppendTo);
- return toAppendTo;
+ fieldPositionHelper(output, fieldPosition, result.length());
+ output.appendTo(result);
+ return result;
}
/**
PatternStringParser.parseToExistingProperties(pattern, properties, ignoreRounding);
}
+ static void fieldPositionHelper(FormattedNumber formatted, FieldPosition fieldPosition, int offset) {
+ fieldPosition.setEndIndex(0); // always return first occurrence
+ boolean found = formatted.nextFieldPosition(fieldPosition);
+ if (found && offset != 0) {
+ fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + offset);
+ fieldPosition.setEndIndex(fieldPosition.getEndIndex() + offset);
+ }
+ }
+
/**
* @internal
* @deprecated This API is ICU internal only.
FormattedNumber result = getUnitFormatterFromCache(NUMBER_FORMATTER_STANDARD,
measure.getUnit(),
perUnit).format(measure.getNumber());
- result.populateFieldPosition(pos, appendTo.length());
+ DecimalFormat.fieldPositionHelper(result, pos, appendTo.length());
result.appendTo(appendTo);
return appendTo;
}
package com.ibm.icu.dev.test.number;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.text.AttributedCharacterIterator;
+import java.text.FieldPosition;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import com.ibm.icu.impl.number.Padder.PadPosition;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.number.CompactNotation;
+import com.ibm.icu.number.FormattedNumber;
import com.ibm.icu.number.FractionRounder;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.ScientificNotation;
import com.ibm.icu.number.UnlocalizedNumberFormatter;
import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberingSystem;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.Currency.CurrencyUsage;
.toString());
}
+ @Test
+ public void fieldPosition() {
+ FormattedNumber fmtd = NumberFormatter.withLocale(ULocale.ENGLISH).format(-9876543210.12);
+ assertEquals("Should have expected format output", "-9,876,543,210.12", fmtd.toString());
+
+ Object[][] expectedFieldPositions = new Object[][]{
+ {NumberFormat.Field.SIGN, 0, 1},
+ {NumberFormat.Field.GROUPING_SEPARATOR, 2, 3},
+ {NumberFormat.Field.GROUPING_SEPARATOR, 6, 7},
+ {NumberFormat.Field.GROUPING_SEPARATOR, 10, 11},
+ {NumberFormat.Field.INTEGER, 1, 14},
+ {NumberFormat.Field.DECIMAL_SEPARATOR, 14, 15},
+ {NumberFormat.Field.FRACTION, 15, 17}};
+
+ AttributedCharacterIterator fpi = fmtd.getFieldIterator();
+ Set<AttributedCharacterIterator.Attribute> allAttributes = fpi.getAllAttributeKeys();
+ assertEquals("All known fields should be in the iterator", 5, allAttributes.size());
+ assertEquals("Iterator should have length of string output", 17, fpi.getEndIndex());
+ int i = 0;
+ for (char c = fpi.first(); c != AttributedCharacterIterator.DONE; c = fpi.next(), i++) {
+ Set<AttributedCharacterIterator.Attribute> currentAttributes = fpi.getAttributes().keySet();
+ int attributesRemaining = currentAttributes.size();
+ for (Object[] cas : expectedFieldPositions) {
+ NumberFormat.Field expectedField = (NumberFormat.Field) cas[0];
+ int expectedBeginIndex = (Integer) cas[1];
+ int expectedEndIndex = (Integer) cas[2];
+ if (expectedBeginIndex > i || expectedEndIndex <= i) {
+ // Field position does not overlap with the current character
+ continue;
+ }
+
+ assertTrue("Current character should have expected field", currentAttributes.contains(expectedField));
+ assertTrue("Field should be a known attribute", allAttributes.contains(expectedField));
+ int actualBeginIndex = fpi.getRunStart(expectedField);
+ int actualEndIndex = fpi.getRunLimit(expectedField);
+ assertEquals(expectedField + " begin index @" + i, expectedBeginIndex, actualBeginIndex);
+ assertEquals(expectedField + " end index @" + i, expectedEndIndex, actualEndIndex);
+ attributesRemaining--;
+ }
+ assertEquals("Should have looked at every field", 0, attributesRemaining);
+ }
+ assertEquals("Should have looked at every character", 17, i);
+
+ // Test the iteration functionality of nextFieldPosition
+ FieldPosition actual = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
+ i = 1;
+ while (fmtd.nextFieldPosition(actual)) {
+ Object[] cas = expectedFieldPositions[i++];
+ NumberFormat.Field expectedField = (NumberFormat.Field) cas[0];
+ int expectedBeginIndex = (Integer) cas[1];
+ int expectedEndIndex = (Integer) cas[2];
+
+ assertEquals(
+ "Next for grouping, field, case #" + i,
+ expectedField,
+ actual.getFieldAttribute());
+ assertEquals(
+ "Next for grouping, begin index, case #" + i,
+ expectedBeginIndex,
+ actual.getBeginIndex());
+ assertEquals(
+ "Next for grouping, end index, case #" + i,
+ expectedEndIndex,
+ actual.getEndIndex());
+ }
+ assertEquals("Should have seen all grouping separators", 4, i);
+
+ // Make sure strings without fraction do not contain fraction field
+ actual = new FieldPosition(NumberFormat.Field.FRACTION);
+ fmtd = NumberFormatter.withLocale(ULocale.ENGLISH).format(5);
+ assertFalse("No fraction part in an integer", fmtd.nextFieldPosition(actual));
+ }
+
@Test
public void plurals() {
// TODO: Expand this test.
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
// Let NumberFormatTest also take care of AttributedCharacterIterator material.
FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
- sb.populateFieldPosition(fp, 0);
+ sb.nextFieldPosition(fp);
assertEquals(str.length(), fp.getBeginIndex());
assertEquals(str.length() * 2, fp.getEndIndex());