ICU-13662 Improving NumberFormatter field position method names and behavior.

X-SVN-Rev: 41313
This commit is contained in:
Shane Carr 2018-05-03 01:34:19 +00:00
parent c035e5bd04
commit 0a0d99ebc8
22 changed files with 446 additions and 147 deletions

View file

@ -428,7 +428,7 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie
}
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;
@ -440,7 +440,7 @@ UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, Fie
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;
@ -482,7 +482,7 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi
}
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;
@ -494,7 +494,7 @@ UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, Fi
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;
@ -542,7 +542,7 @@ UnicodeString&
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;
@ -1227,6 +1227,17 @@ const numparse::impl::NumberParserImpl* DecimalFormat::getCurrencyParser(UErrorC
}
}
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)

View file

@ -1,50 +0,0 @@
// © 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 */

View file

@ -534,7 +534,6 @@
<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" />

View file

@ -857,9 +857,6 @@
<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>

View file

@ -639,7 +639,6 @@
<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" />

View file

@ -13,7 +13,6 @@
#include "number_utypes.h"
#include "unicode/numberformatter.h"
#include "unicode/unumberformatter.h"
#include "fieldposutil.h"
using namespace icu;
using namespace icu::number;
@ -158,23 +157,30 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf
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;
@ -184,7 +190,7 @@ unumf_resultGetAllFields(const UFormattedNumber* uresult, UFieldPositionIterator
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

View file

@ -776,7 +776,21 @@ void FormattedNumber::populateFieldPosition(FieldPosition& fieldPosition, UError
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) {
@ -787,7 +801,18 @@ void FormattedNumber::populateFieldPositionIterator(FieldPositionIterator& itera
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 {

View file

@ -413,23 +413,24 @@ bool NumberStringBuilder::contentEquals(const NumberStringBuilder &other) 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];
@ -439,10 +440,10 @@ void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offse
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) {
@ -450,14 +451,17 @@ void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offse
}
}
// 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)) {

View file

@ -101,9 +101,9 @@ class U_I18N_API NumberStringBuilder : public UMemory {
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;

View file

@ -60,6 +60,7 @@ class CompactDecimalFormat;
namespace number {
class LocalizedNumberFormatter;
class FormattedNumber;
namespace impl {
class DecimalQuantity;
struct DecimalFormatFields;
@ -2111,6 +2112,9 @@ class U_I18N_API DecimalFormat : public NumberFormat {
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;

View file

@ -2378,11 +2378,45 @@ class U_I18N_API FormattedNumber : public UMemory {
* 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.
@ -2394,11 +2428,27 @@ class U_I18N_API FormattedNumber : public UMemory {
* 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
/**

View file

@ -532,30 +532,44 @@ unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t buf
/**
* 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
@ -570,8 +584,8 @@ unumf_resultGetField(const UFormattedNumber* uresult, UFieldPosition* ufpos, UEr
* @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);
/**

View file

@ -83,14 +83,14 @@ static void TestSkeletonFormatToFields() {
// 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},
@ -128,6 +128,18 @@ static void TestSkeletonFormatToFields() {
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);

View file

@ -67,6 +67,7 @@ class NumberFormatterApiTest : public IntlTest {
void scale();
void locale();
void formatTypes();
void fieldPosition();
void errors();
void validRanges();
void copyMove();

View file

@ -81,6 +81,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(scale);
TESTCASE_AUTO(locale);
TESTCASE_AUTO(formatTypes);
TESTCASE_AUTO(fieldPosition);
TESTCASE_AUTO(errors);
TESTCASE_AUTO(validRanges);
TESTCASE_AUTO(copyMove);
@ -2066,6 +2067,94 @@ void NumberFormatterApiTest::formatTypes() {
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(
@ -2293,7 +2382,7 @@ void NumberFormatterApiTest::localPointerCAPI() {
// 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);

View file

@ -191,7 +191,7 @@ void NumberStringBuilderTest::testFields() {
// 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());

View file

@ -486,10 +486,9 @@ public class NumberStringBuilder implements CharSequence {
*
* @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) {
@ -500,21 +499,22 @@ public class NumberStringBuilder implements CharSequence {
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.
@ -522,10 +522,10 @@ public class NumberStringBuilder implements CharSequence {
&& _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) {
@ -533,14 +533,17 @@ public class NumberStringBuilder implements CharSequence {
}
}
// 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;

View file

@ -85,43 +85,91 @@ public class FormattedNumber {
*
* @param fieldPosition
* The FieldPosition to populate with the start and end indices of the desired field.
* @draft ICU 60
* @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) {
// in case any users were depending on the old behavior:
fieldPosition.setEndIndex(0);
nextFieldPosition(fieldPosition);
}
/**
* 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
*/
public void populateFieldPosition(FieldPosition fieldPosition) {
populateFieldPosition(fieldPosition, 0);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@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.
* @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 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 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();
}
/**

View file

@ -709,7 +709,7 @@ public class DecimalFormat extends NumberFormat {
@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;
}
@ -722,7 +722,7 @@ public class DecimalFormat extends NumberFormat {
@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;
}
@ -735,7 +735,7 @@ public class DecimalFormat extends NumberFormat {
@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;
}
@ -749,7 +749,7 @@ public class DecimalFormat extends NumberFormat {
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;
}
@ -762,7 +762,7 @@ public class DecimalFormat extends NumberFormat {
@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;
}
@ -786,11 +786,11 @@ public class DecimalFormat extends NumberFormat {
* @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;
}
/**
@ -2566,6 +2566,15 @@ public class DecimalFormat extends NumberFormat {
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.

View file

@ -388,7 +388,7 @@ public class MeasureFormat extends UFormat {
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;
}

View file

@ -3,6 +3,7 @@
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;
@ -11,6 +12,8 @@ import java.lang.reflect.InvocationTargetException;
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;
@ -26,6 +29,7 @@ import com.ibm.icu.impl.number.Padder;
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;
@ -40,6 +44,7 @@ import com.ibm.icu.number.Scale;
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;
@ -2040,6 +2045,79 @@ public class NumberFormatterApiTest {
.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.

View file

@ -170,7 +170,7 @@ public class NumberStringBuilderTest {
// 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());