Skip to content

Commit

Permalink
ICU-13742 Implementing number skeletons in MessageFormat.
Browse files Browse the repository at this point in the history
X-SVN-Rev: 41377
  • Loading branch information
sffc committed May 15, 2018
1 parent c3fa4e9 commit b347a14
Show file tree
Hide file tree
Showing 16 changed files with 529 additions and 29 deletions.
22 changes: 16 additions & 6 deletions icu4c/source/i18n/msgfmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "unicode/decimfmt.h"
#include "unicode/localpointer.h"
#include "unicode/msgfmt.h"
#include "unicode/numberformatter.h"
#include "unicode/plurfmt.h"
#include "unicode/rbnf.h"
#include "unicode/selfmt.h"
Expand Down Expand Up @@ -1700,12 +1701,21 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin
formattableType = Formattable::kLong;
fmt = createIntegerFormat(fLocale, ec);
break;
default: // pattern
fmt = NumberFormat::createInstance(fLocale, ec);
if (fmt) {
DecimalFormat* decfmt = dynamic_cast<DecimalFormat*>(fmt);
if (decfmt != NULL) {
decfmt->applyPattern(style,parseError,ec);
default: // pattern or skeleton
int32_t i = 0;
for (; PatternProps::isWhiteSpace(style.charAt(i)); i++);
if (style.compare(i, 2, u"::", 0, 2) == 0) {
// Skeleton
UnicodeString skeleton = style.tempSubString(i + 2);
fmt = number::NumberFormatter::fromSkeleton(skeleton, ec).locale(fLocale).toFormat(ec);
} else {
// Pattern
fmt = NumberFormat::createInstance(fLocale, ec);
if (fmt) {
auto* decfmt = dynamic_cast<DecimalFormat*>(fmt);
if (decfmt != nullptr) {
decfmt->applyPattern(style, parseError, ec);
}
}
}
break;
Expand Down
42 changes: 25 additions & 17 deletions icu4c/source/i18n/number_fluent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "number_formatimpl.h"
#include "umutex.h"
#include "number_skeletons.h"
#include "number_utils.h"
#include "number_utypes.h"
#include "util.h"

Expand Down Expand Up @@ -575,23 +576,6 @@ const NumberingSystem* SymbolsWrapper::getNumberingSystem() const {
}


FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
}

FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
delete fResults;
fResults = src.fResults;
fErrorCode = src.fErrorCode;
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
return *this;
}

FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const {
if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); }
auto results = new UFormattedNumberData();
Expand Down Expand Up @@ -745,6 +729,30 @@ int32_t LocalizedNumberFormatter::getCallCount() const {
return umtx_loadAcquire(*callCount);
}

Format* LocalizedNumberFormatter::toFormat(UErrorCode& status) const {
LocalPointer<LocalizedNumberFormatterAsFormat> retval(
new LocalizedNumberFormatterAsFormat(*this, fMacros.locale), status);
return retval.orphan();
}


FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
}

FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT {
delete fResults;
fResults = src.fResults;
fErrorCode = src.fErrorCode;
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
return *this;
}

UnicodeString FormattedNumber::toString() const {
UErrorCode localStatus = U_ZERO_ERROR;
return toString(localStatus);
Expand Down
80 changes: 80 additions & 0 deletions icu4c/source/i18n/number_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "double-conversion.h"
#include "uresimp.h"
#include "ureslocs.h"
#include "number_utypes.h"

using namespace icu;
using namespace icu::number;
Expand Down Expand Up @@ -47,6 +48,85 @@ doGetPattern(UResourceBundle* res, const char* nsName, const char* patternKey, U
}


LocalizedNumberFormatterAsFormat::LocalizedNumberFormatterAsFormat(
const LocalizedNumberFormatter& formatter, const Locale& locale)
: fFormatter(formatter), fLocale(locale) {
const char* localeName = locale.getName();
setLocaleIDs(localeName, localeName);
}

LocalizedNumberFormatterAsFormat::~LocalizedNumberFormatterAsFormat() = default;

UBool LocalizedNumberFormatterAsFormat::operator==(const Format& other) const {
auto* _other = dynamic_cast<const LocalizedNumberFormatterAsFormat*>(&other);
if (_other == nullptr) {
return false;
}
// TODO: Change this to use LocalizedNumberFormatter::operator== if it is ever proposed.
// This implementation is fine, but not particularly efficient.
UErrorCode localStatus = U_ZERO_ERROR;
return fFormatter.toSkeleton(localStatus) == _other->fFormatter.toSkeleton(localStatus);
}

Format* LocalizedNumberFormatterAsFormat::clone() const {
return new LocalizedNumberFormatterAsFormat(*this);
}

UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo,
FieldPosition& pos, UErrorCode& status) const {
if (U_FAILURE(status)) { return appendTo; }
UFormattedNumberData data;
obj.populateDecimalQuantity(data.quantity, status);
if (U_FAILURE(status)) {
return appendTo;
}
fFormatter.formatImpl(&data, status);
if (U_FAILURE(status)) {
return appendTo;
}
// always return first occurrence:
pos.setBeginIndex(0);
pos.setEndIndex(0);
bool found = data.string.nextFieldPosition(pos, status);
if (found && appendTo.length() != 0) {
pos.setBeginIndex(pos.getBeginIndex() + appendTo.length());
pos.setEndIndex(pos.getEndIndex() + appendTo.length());
}
appendTo.append(data.string.toTempUnicodeString());
return appendTo;
}

UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo,
FieldPositionIterator* posIter,
UErrorCode& status) const {
if (U_FAILURE(status)) { return appendTo; }
UFormattedNumberData data;
obj.populateDecimalQuantity(data.quantity, status);
if (U_FAILURE(status)) {
return appendTo;
}
fFormatter.formatImpl(&data, status);
if (U_FAILURE(status)) {
return appendTo;
}
appendTo.append(data.string.toTempUnicodeString());
if (posIter != nullptr) {
data.string.getAllFieldPositions(*posIter, status);
}
return appendTo;
}

void LocalizedNumberFormatterAsFormat::parseObject(const UnicodeString&, Formattable&,
ParsePosition& parse_pos) const {
// Not supported.
parse_pos.setErrorIndex(0);
}

const LocalizedNumberFormatter& LocalizedNumberFormatterAsFormat::getNumberFormatter() const {
return fFormatter;
}


const char16_t* utils::getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style,
UErrorCode& status) {
const char* patternKey;
Expand Down
76 changes: 76 additions & 0 deletions icu4c/source/i18n/number_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,82 @@ struct MicroProps : public MicroPropsGenerator {
bool exhausted = false;
};


/**
* A wrapper around LocalizedNumberFormatter implementing the Format interface, enabling improved
* compatibility with other APIs.
*
* @draft ICU 62
* @see NumberFormatter
*/
class U_I18N_API LocalizedNumberFormatterAsFormat : public Format {
public:
LocalizedNumberFormatterAsFormat(const LocalizedNumberFormatter& formatter, const Locale& locale);

/**
* Destructor.
*/
~LocalizedNumberFormatterAsFormat() U_OVERRIDE;

/**
* Equals operator.
*/
UBool operator==(const Format& other) const U_OVERRIDE;

/**
* Creates a copy of this object.
*/
Format* clone() const U_OVERRIDE;

/**
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a
* number type.
*/
UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos,
UErrorCode& status) const U_OVERRIDE;

/**
* Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a
* number type.
*/
UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPositionIterator* posIter,
UErrorCode& status) const U_OVERRIDE;

/**
* Not supported: sets an error index and returns.
*/
void parseObject(const UnicodeString& source, Formattable& result,
ParsePosition& parse_pos) const U_OVERRIDE;

/**
* Gets the LocalizedNumberFormatter that this wrapper class uses to format numbers.
*
* For maximum efficiency, this function returns by const reference. You must copy the return value
* into a local variable if you want to use it beyond the lifetime of the current object:
*
* <pre>
* LocalizedNumberFormatter localFormatter = fmt->getNumberFormatter();
* </pre>
*
* You can however use the return value directly when chaining:
*
* <pre>
* FormattedNumber result = fmt->getNumberFormatter().formatDouble(514.23, status);
* </pre>
*
* @return The unwrapped LocalizedNumberFormatter.
*/
const LocalizedNumberFormatter& getNumberFormatter() const;

private:
LocalizedNumberFormatter fFormatter;

// Even though the locale is inside the LocalizedNumberFormatter, we have to keep it here, too, because
// LocalizedNumberFormatter doesn't have a getLocale() method, and ICU-TC didn't want to add one.
Locale fLocale;
};


enum CldrPatternStyle {
CLDR_PATTERN_STYLE_DECIMAL,
CLDR_PATTERN_STYLE_CURRENCY,
Expand Down
15 changes: 15 additions & 0 deletions icu4c/source/i18n/unicode/numberformatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,21 @@ class U_I18N_API LocalizedNumberFormatter

#endif

/**
* Creates a representation of this LocalizedNumberFormat as an icu::Format, enabling the use
* of this number formatter with APIs that need an object of that type, such as MessageFormat.
*
* This API is not intended to be used other than for enabling API compatibility. The formatDouble,
* formatInt, and formatDecimal methods should normally be used when formatting numbers, not the Format
* object returned by this method.
*
* The caller owns the returned object and must delete it when finished.
*
* @return A Format wrapping this LocalizedNumberFormatter.
* @draft ICU 62
*/
Format* toFormat(UErrorCode& status) const;

/**
* Default constructor: puts the formatter into a valid but undefined state.
*
Expand Down
1 change: 1 addition & 0 deletions icu4c/source/test/intltest/numbertest.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class NumberFormatterApiTest : public IntlTest {
void locale();
void formatTypes();
void fieldPosition();
void toFormat();
void errors();
void validRanges();
void copyMove();
Expand Down
26 changes: 26 additions & 0 deletions icu4c/source/test/intltest/numbertest_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "unicode/unum.h"
#include "unicode/numberformatter.h"
#include "number_types.h"
#include "number_utils.h"
#include "numbertest.h"
#include "unicode/utypes.h"

Expand Down Expand Up @@ -82,6 +83,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(locale);
TESTCASE_AUTO(formatTypes);
TESTCASE_AUTO(fieldPosition);
TESTCASE_AUTO(toFormat);
TESTCASE_AUTO(errors);
TESTCASE_AUTO(validRanges);
TESTCASE_AUTO(copyMove);
Expand Down Expand Up @@ -2155,6 +2157,30 @@ void NumberFormatterApiTest::fieldPosition() {
assertFalse(u"No fraction part in an integer", fmtd.nextFieldPosition(actual, status));
}

void NumberFormatterApiTest::toFormat() {
IcuTestErrorCode status(*this, "icuFormat");
LocalizedNumberFormatter lnf = NumberFormatter::withLocale("fr")
.precision(Precision::fixedFraction(3));
LocalPointer<Format> format(lnf.toFormat(status), status);
FieldPosition fpos(UNUM_DECIMAL_SEPARATOR_FIELD);
UnicodeString sb;
format->format(514.23, sb, fpos, status);
assertEquals("Should correctly format number", u"514,230", sb);
assertEquals("Should find decimal separator", 3, fpos.getBeginIndex());
assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex());
assertEquals(
"ICU Format should round-trip",
lnf.toSkeleton(status),
dynamic_cast<LocalizedNumberFormatterAsFormat*>(format.getAlias())->getNumberFormatter()
.toSkeleton(status));

FieldPositionIterator fpi1;
lnf.formatDouble(514.23, status).getAllFieldPositions(fpi1, status);
FieldPositionIterator fpi2;
format->format(514.23, sb.remove(), &fpi2, status);
assertTrue("Should produce same field position iterator", fpi1 == fpi2);
}

void NumberFormatterApiTest::errors() {
LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).precision(
Precision::fixedFraction(
Expand Down
35 changes: 35 additions & 0 deletions icu4c/source/test/intltest/tmsgfmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ TestMessageFormat::runIndexedTest(int32_t index, UBool exec,
TESTCASE_AUTO(TestSelectOrdinal);
TESTCASE_AUTO(TestDecimals);
TESTCASE_AUTO(TestArgIsPrefixOfAnother);
TESTCASE_AUTO(TestMessageFormatNumberSkeleton);
TESTCASE_AUTO_END;
}

Expand Down Expand Up @@ -1992,4 +1993,38 @@ void TestMessageFormat::TestArgIsPrefixOfAnother() {
assertEquals("aa aaa", "AB ABC", mf3.format(argNames + 1, args + 1, 2, result.remove(), errorCode));
}

void TestMessageFormat::TestMessageFormatNumberSkeleton() {
IcuTestErrorCode status(*this, "TestMessageFormatNumberSkeleton");

static const struct TestCase {
const char16_t* messagePattern;
const char* localeName;
double arg;
const char16_t* expected;
} cases[] = {
{ u"{0,number,::percent}", "en", 50, u"50%" },
{ u"{0,number,::percent scale/100}", "en", 0.5, u"50%" },
{ u"{0,number, :: percent scale/100 }", "en", 0.5, u"50%" },
{ u"{0,number,::currency/USD}", "en", 23, u"$23.00" },
{ u"{0,number,::precision-integer}", "en", 514.23, u"514" },
{ u"{0,number,::.000}", "en", 514.23, u"514.230" },
{ u"{0,number,::.}", "en", 514.23, u"514" },
{ u"{0,number,::}", "fr", 514.23, u"514,23" },
{ u"Cost: {0,number,::currency/EUR}.", "en", 4.3, u"Cost: €4.30." },
{ u"{0,number,'::'0.00}", "en", 50, u"::50.00" }, // pattern literal
};

for (auto& cas : cases) {
status.setScope(cas.messagePattern);
MessageFormat msgf(cas.messagePattern, cas.localeName, status);
UnicodeString sb;
FieldPosition fpos(0);
Formattable argsArray[] = {{cas.arg}};
Formattable args(argsArray, 1);
msgf.format(args, sb, status);

assertEquals(cas.messagePattern, cas.expected, sb);
}
}

#endif /* #if !UCONFIG_NO_FORMATTING */
Loading

0 comments on commit b347a14

Please sign in to comment.