Merge pull request #5 from hugovdm/usage_glue

Implement Usage "Glue Code"
This commit is contained in:
Hugo 2020-07-01 19:50:13 +02:00 committed by GitHub
commit 08132e7a33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 666 additions and 100 deletions

View file

@ -323,11 +323,6 @@ public:
* @return the array pointer
*/
T *getAlias() const { return ptr; }
/**
* Access without ownership change.
* @return the array pointer
*/
const T *getConstAlias() const { return ptr; }
/**
* Returns the array limit. Simple convenience method.
* @return getAlias()+getCapacity()
@ -793,12 +788,12 @@ public:
return this->fCount;
}
T** getAlias() const {
T** getAlias() {
return this->fPool.getAlias();
}
const T *const *getConstAlias() const {
return this->fPool.getConstAlias();
const T *const *getAlias() const {
return this->fPool.getAlias();
}
/**
@ -822,7 +817,7 @@ public:
}
/**
* Append all the items from another MaybeStackVector to this one.
* Append copies of all the items from another MaybeStackVector to this one.
*/
void appendAll(const MaybeStackVector& other, UErrorCode& status) {
for (int32_t i = 0; i < other.fCount; i++) {

View file

@ -25,7 +25,7 @@ class FormattedValueStringBuilderImpl;
*
* <ol>
* <li>Efficient prepend as well as append.
* <li>Keeps tracks of Fields in an efficient manner.
* <li>Keeps track of Fields in an efficient manner.
* </ol>
*
* See also FormattedValueStringBuilderImpl.

View file

@ -213,6 +213,7 @@
<ClCompile Include="number_rounding.cpp" />
<ClCompile Include="number_scientific.cpp" />
<ClCompile Include="formatted_string_builder.cpp" />
<ClCompile Include="number_usageprefs.cpp" />
<ClCompile Include="number_utils.cpp" />
<ClCompile Include="number_mapper.cpp" />
<ClCompile Include="number_multiplier.cpp" />
@ -485,6 +486,7 @@
<ClInclude Include="number_scientific.h" />
<ClInclude Include="formatted_string_builder.h" />
<ClInclude Include="number_types.h" />
<ClCompile Include="number_usageprefs.h" />
<ClInclude Include="number_utypes.h" />
<ClInclude Include="number_utils.h" />
<ClInclude Include="number_mapper.h" />

View file

@ -20,7 +20,7 @@ namespace impl {
class DecNum;
/**
* An class for representing a number to be processed by the decimal formatting pipeline. Includes
* A class for representing a number to be processed by the decimal formatting pipeline. Includes
* methods for rounding, plural rules, and decimal digit extraction.
*
* <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
@ -217,7 +217,13 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
DecimalQuantity &setToDouble(double n);
/** decNumber is similar to BigDecimal in Java. */
/**
* Produces a DecimalQuantity that was parsed from a string by the decNumber
* C Library.
*
* decNumber is similar to BigDecimal in Java, and supports parsing strings
* such as "123.456621E+40".
*/
DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status);
/** Internal method if the caller already has a DecNum. */

View file

@ -274,6 +274,20 @@ Derived NumberFormatterSettings<Derived>::scale(const Scale& scale)&& {
return move;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::usage(const StringPiece usage) const& {
Derived copy(*this);
copy.fMacros.usage.set(usage);
return copy;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::usage(const StringPiece usage)&& {
Derived move(std::move(*this));
move.fMacros.usage.set(usage);
return move;
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::padding(const Padder& padder) const& {
Derived copy(*this);
@ -702,9 +716,9 @@ LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErro
void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const {
if (computeCompiled(status)) {
fCompiled->format(results->quantity, results->getStringRef(), status);
fCompiled->format(results, status);
} else {
NumberFormatterImpl::formatStatic(fMacros, results->quantity, results->getStringRef(), status);
NumberFormatterImpl::formatStatic(fMacros, results, status);
}
if (U_FAILURE(status)) {
return;

View file

@ -32,13 +32,16 @@ NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& s
: NumberFormatterImpl(macros, true, status) {
}
int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
FormattedStringBuilder& outString, UErrorCode& status) {
int32_t NumberFormatterImpl::formatStatic(const MacroProps &macros, UFormattedNumberData *results,
UErrorCode &status) {
DecimalQuantity &inValue = results->quantity;
FormattedStringBuilder &outString = results->getStringRef();
NumberFormatterImpl impl(macros, false, status);
MicroProps& micros = impl.preProcessUnsafe(inValue, status);
if (U_FAILURE(status)) { return 0; }
int32_t length = writeNumber(micros, inValue, outString, 0, status);
length += writeAffixes(micros, outString, 0, length, status);
results->outputUnit = std::move(micros.outputUnit);
return length;
}
@ -54,13 +57,15 @@ int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Sig
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
// See MicroProps::processQuantity() for details.
int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, FormattedStringBuilder& outString,
UErrorCode& status) const {
int32_t NumberFormatterImpl::format(UFormattedNumberData *results, UErrorCode &status) const {
DecimalQuantity &inValue = results->quantity;
FormattedStringBuilder &outString = results->getStringRef();
MicroProps micros;
preProcess(inValue, micros, status);
if (U_FAILURE(status)) { return 0; }
int32_t length = writeNumber(micros, inValue, outString, 0, status);
length += writeAffixes(micros, outString, 0, length, status);
results->outputUnit = std::move(micros.outputUnit);
return length;
}
@ -222,6 +227,19 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
/////////////////////////////////////////////////////////////////////////////////////
// Unit Preferences and Conversions as our first step
if (macros.usage.isSet()) {
if (!isCldrUnit) {
// We only support "usage" when the input unit is a CLDR Unit.
status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
auto usagePrefsHandler =
new UsagePrefsHandler(macros.locale, macros.unit, macros.usage.fUsage, chain, status);
fUsagePrefsHandler.adoptInsteadAndCheckErrorCode(usagePrefsHandler, status);
chain = fUsagePrefsHandler.getAlias();
}
// Multiplier
if (macros.scale.isValid()) {
fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
@ -330,7 +348,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status);
}
if (safe) {
fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
fImmutablePatternModifier.adoptInsteadAndCheckErrorCode(patternModifier->createImmutable(status),
status);
}
if (U_FAILURE(status)) {
return nullptr;
@ -338,24 +357,26 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
// Outer modifier (CLDR units and currency long names)
if (isCldrUnit) {
fLongNameHandler.adoptInstead(
LongNameHandler::forMeasureUnit(
macros.locale,
macros.unit,
macros.perUnit,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status));
chain = fLongNameHandler.getAlias();
if (macros.usage.isSet()) {
fLongNameMultiplexer.adoptInsteadAndCheckErrorCode(
LongNameMultiplexer::forMeasureUnits(
macros.locale, *fUsagePrefsHandler->getOutputUnits(), unitWidth,
resolvePluralRules(macros.rules, macros.locale, status), chain, status),
status);
chain = fLongNameMultiplexer.getAlias();
} else {
fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain, fLongNameHandler.getAlias(), status);
chain = fLongNameHandler.getAlias();
}
} else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
fLongNameHandler.adoptInstead(
LongNameHandler::forCurrencyLongNames(
macros.locale,
currency,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status));
fLongNameHandler.adoptInsteadAndCheckErrorCode(
LongNameHandler::forCurrencyLongNames(
macros.locale, currency, resolvePluralRules(macros.rules, macros.locale, status), chain,
status),
status);
chain = fLongNameHandler.getAlias();
} else {
// No outer modifier required
@ -379,6 +400,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
safe,
chain,
status);
if (U_FAILURE(status)) {
return nullptr;
}
if (newCompactHandler == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;

View file

@ -10,11 +10,13 @@
#include "number_types.h"
#include "formatted_string_builder.h"
#include "number_patternstring.h"
#include "number_usageprefs.h"
#include "number_utils.h"
#include "number_patternmodifier.h"
#include "number_longnames.h"
#include "number_compact.h"
#include "number_microprops.h"
#include "number_utypes.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
@ -34,9 +36,8 @@ class NumberFormatterImpl : public UMemory {
/**
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
*/
static int32_t
formatStatic(const MacroProps &macros, DecimalQuantity &inValue, FormattedStringBuilder &outString,
UErrorCode &status);
static int32_t formatStatic(const MacroProps &macros, UFormattedNumberData *results,
UErrorCode &status);
/**
* Prints only the prefix and suffix; used for DecimalFormat getters.
@ -51,7 +52,7 @@ class NumberFormatterImpl : public UMemory {
/**
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
int32_t format(DecimalQuantity& inValue, FormattedStringBuilder& outString, UErrorCode& status) const;
int32_t format(UFormattedNumberData *results, UErrorCode &status) const;
/**
* Like format(), but saves the result into an output MicroProps without additional processing.
@ -82,7 +83,9 @@ class NumberFormatterImpl : public UMemory {
int32_t end, UErrorCode& status);
private:
// Head of the MicroPropsGenerator linked list:
// Head of the MicroPropsGenerator linked list. Subclasses' processQuantity
// methods process this list in a parent-first order, such that the last
// item added, which this points to, typically has its logic executed last.
const MicroPropsGenerator *fMicroPropsGenerator = nullptr;
// Tail of the list:
@ -90,13 +93,15 @@ class NumberFormatterImpl : public UMemory {
// Other fields possibly used by the number formatting pipeline:
// TODO: Convert more of these LocalPointers to value objects to reduce the number of news?
LocalPointer<const UsagePrefsHandler> fUsagePrefsHandler;
LocalPointer<const DecimalFormatSymbols> fSymbols;
LocalPointer<const PluralRules> fRules;
LocalPointer<const ParsedPatternInfo> fPatternInfo;
LocalPointer<const ScientificHandler> fScientificHandler;
LocalPointer<MutablePatternModifier> fPatternModifier;
LocalPointer<ImmutablePatternModifier> fImmutablePatternModifier;
LocalPointer<const LongNameHandler> fLongNameHandler;
LocalPointer<LongNameHandler> fLongNameHandler;
LocalPointer<const LongNameMultiplexer> fLongNameMultiplexer;
LocalPointer<const CompactHandler> fCompactHandler;
// Value objects possibly used by the number formatting pipeline:

View file

@ -22,7 +22,17 @@ using namespace icu::number::impl;
namespace {
/**
* Display Name (this format has no placeholder).
*
* Used as an index into the LongNameHandler::simpleFormats array. Units
* resources cover the normal set of PluralRules keys, as well as `dnam` and
* `per` forms.
*/
constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
/**
* @copydoc DNAM_INDEX
*/
constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
@ -87,6 +97,12 @@ class PluralTableSink : public ResourceSink {
// NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed.
// Populates outArray with `locale`-specific values for `unit` through use of
// PluralTableSink, reading from resources *unitsNarrow* and *unitsShort* (for
// width UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width
// UNUM_UNIT_WIDTH_SHORT). For other widths, it would read just "units".
//
// outArray must be of fixed length ARRAY_LENGTH.
void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width,
UnicodeString *outArray, UErrorCode &status) {
PluralTableSink sink(outArray);
@ -184,14 +200,19 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
} // namespace
LongNameHandler*
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status) {
// TODO(units,hugovdm): deal properly with "perUnit" parameter here:
void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
const PluralRules *rules, const MicroPropsGenerator *parent,
LongNameHandler *fillIn, UErrorCode &status) {
if (fillIn == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
if (uprv_strlen(unitRef.getType()) == 0 || uprv_strlen(perUnit.getType()) == 0) {
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an error code.
status = U_UNSUPPORTED_ERROR;
return nullptr;
return;
}
MeasureUnit unit = unitRef;
@ -203,59 +224,75 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, c
unit = resolved;
} else {
// No simplified form is available.
return forCompoundUnit(loc, unit, perUnit, width, rules, parent, status);
forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
return;
}
}
auto* result = new LongNameHandler(rules, parent);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
UnicodeString simpleFormats[ARRAY_LENGTH];
getMeasureData(loc, unit, width, simpleFormats, status);
if (U_FAILURE(status)) { return result; }
result->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
return result;
if (U_FAILURE(status)) {
return;
}
fillIn->rules = rules;
fillIn->parent = parent;
fillIn->simpleFormatsToModifiers(simpleFormats, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD},
status);
}
LongNameHandler*
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status) {
auto* result = new LongNameHandler(rules, parent);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
// TODO(units,hugovdm): deal properly with "perUnit" parameter here:
void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit,
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
const PluralRules *rules, const MicroPropsGenerator *parent,
LongNameHandler *fillIn, UErrorCode &status) {
if (fillIn == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
UnicodeString primaryData[ARRAY_LENGTH];
getMeasureData(loc, unit, width, primaryData, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
UnicodeString secondaryData[ARRAY_LENGTH];
getMeasureData(loc, perUnit, width, secondaryData, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
UnicodeString perUnitFormat;
if (!secondaryData[PER_INDEX].isBogus()) {
perUnitFormat = secondaryData[PER_INDEX];
} else {
UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
// Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
SimpleFormatter secondaryCompiled(secondaryFormat, 0, 1, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
// TODO: Why does UnicodeString need to be explicit in the following line?
compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
if (U_FAILURE(status)) { return result; }
if (U_FAILURE(status)) {
return;
}
}
result->multiSimpleFormatsToModifiers(primaryData, perUnitFormat, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
return result;
fillIn->rules = rules;
fillIn->parent = parent;
fillIn->multiSimpleFormatsToModifiers(primaryData, perUnitFormat,
{UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, status);
}
UnicodeString LongNameHandler::getUnitDisplayName(
@ -338,7 +375,9 @@ void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFor
void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
parent->processQuantity(quantity, micros, status);
if (parent != NULL) {
parent->processQuantity(quantity, micros, status);
}
StandardPlural::Form pluralForm = utils::getPluralSafe(micros.rounder, rules, quantity, status);
micros.modOuter = &fModifiers[pluralForm];
}
@ -347,4 +386,49 @@ const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::
return &fModifiers[plural];
}
LongNameMultiplexer *
LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<MeasureUnit> &units,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status) {
LocalPointer<LongNameMultiplexer> result(new LongNameMultiplexer(parent), status);
if (U_FAILURE(status)) {
return nullptr;
}
U_ASSERT(units.length() > 0);
result->fMeasureUnits.adoptInstead(new MeasureUnit[units.length()]);
for (int32_t i = 0, length = units.length(); i < length; i++) {
// Create empty new LongNameHandler:
LongNameHandler *lnh =
result->fLongNameHandlers.emplaceBackAndCheckErrorCode(status);
result->fMeasureUnits[i] = *units[i];
// Fill in LongNameHandler:
LongNameHandler::forMeasureUnit(loc, *units[i],
MeasureUnit(), // TODO(units): deal with COMPOUND and MIXED units
width, rules, NULL, lnh, status);
if (U_FAILURE(status)) {
return nullptr;
}
}
return result.orphan();
}
void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
// We call parent->processQuantity() from the Multiplexer, instead of
// letting LongNameHandler handle it: we don't know which LongNameHandler to
// call until we've called the parent!
fParent->processQuantity(quantity, micros, status);
// Call the correct LongNameHandler based on outputUnit
for (int i = 0; i < fLongNameHandlers.length(); i++) {
if (fMeasureUnits[i] == micros.outputUnit) {
fLongNameHandlers[i]->processQuantity(quantity, micros, status);
return;
}
}
// We shouldn't receive any outputUnit for which we haven't already got a
// LongNameHandler:
status = U_INTERNAL_PROGRAM_ERROR;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -7,6 +7,7 @@
#ifndef __NUMBER_LONGNAMES_H__
#define __NUMBER_LONGNAMES_H__
#include "cmemory.h"
#include "unicode/uversion.h"
#include "number_utils.h"
#include "number_modifiers.h"
@ -33,10 +34,10 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
static LongNameHandler*
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
UErrorCode &status);
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const U_OVERRIDE;
@ -45,22 +46,63 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
private:
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
// Not owned
const PluralRules *rules;
// Not owned
const MicroPropsGenerator *parent;
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
: rules(rules), parent(parent) {}
: rules(rules), parent(parent) {
}
static LongNameHandler*
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
LongNameHandler() : rules(nullptr), parent(nullptr) {
}
friend class MemoryPool<LongNameHandler>; // To enable emplaceBack();
friend class NumberFormatterImpl;
static void forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
UErrorCode &status);
void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status);
void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
Field field, UErrorCode &status);
};
const int MAX_PREFS_COUNT = 10;
/**
* A MicroPropsGenerator that multiplexes between different LongNameHandlers,
* depending on the outputUnit (micros.helpers.outputUnit should be set earlier
* in the chain).
*/
class LongNameMultiplexer : public MicroPropsGenerator, public UMemory {
public:
// FIXME: docstring?
static LongNameMultiplexer *forMeasureUnits(const Locale &loc,
const MaybeStackVector<MeasureUnit> &units,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
void processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const U_OVERRIDE;
private:
/**
* Because we only know which LongNameHandler we wish to call after calling
* earlier MicroPropsGenerators in the chain, LongNameMultiplexer keeps the
* parent link, while the LongNameHandlers are given no parents.
*/
MaybeStackVector<LongNameHandler> fLongNameHandlers;
LocalArray<MeasureUnit> fMeasureUnits;
const MicroPropsGenerator *fParent;
LongNameMultiplexer(const MicroPropsGenerator *parent) : fParent(parent) {
}
};
} // namespace impl
} // namespace number
U_NAMESPACE_END

View file

@ -22,6 +22,11 @@
U_NAMESPACE_BEGIN namespace number {
namespace impl {
// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do
// we want to better document why? There's an explanation for processQuantity:
// * As MicroProps is the "base instance", this implementation of
// * MicoPropsGenerator::processQuantity() just ensures that the output
// * `micros` is correctly initialized.
struct MicroProps : public MicroPropsGenerator {
// NOTE: All of these fields are properly initialized in NumberFormatterImpl.
@ -49,6 +54,8 @@ struct MicroProps : public MicroPropsGenerator {
MultiplierFormatHandler multiplier;
} helpers;
// The MeasureUnit with which the output measurement is represented.
MeasureUnit outputUnit;
MicroProps() = default;
@ -56,7 +63,27 @@ struct MicroProps : public MicroPropsGenerator {
MicroProps& operator=(const MicroProps& other) = default;
void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE {
/**
* As MicroProps is the "base instance", this implementation of
* MicoPropsGenerator::processQuantity() just ensures that the output
* `micros` is correctly initialized.
*
* For the "safe" invocation of this function, micros must not be *this,
* such that a copy of the base instance is made. For the "unsafe" path,
* this function can be used only once, because the base MicroProps instance
* will be modified and thus not be available for re-use.
*
* TODO(units,hugovdm): deal with outputUnits: processQuantity may need to
* return a MeasurementUnit instance too, in some fashion. Or do we just
* keep it in micros.outputUnit?
*
* @param quantity The quantity for consideration and optional mutation.
* @param micros The MicroProps instance to populate. If this parameter is
* not already `*this`, it will be overwritten with a copy of `*this`.
*/
void processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const U_OVERRIDE {
(void) quantity;
(void) status;
if (this == &micros) {
// Unsafe path: no need to perform a copy.
@ -65,12 +92,14 @@ struct MicroProps : public MicroPropsGenerator {
U_ASSERT(exhausted);
} else {
// Safe path: copy self into the output micros.
U_ASSERT(!exhausted);
micros = *this;
}
}
private:
// Internal fields:
// FIXME: describe?
bool exhausted = false;
};

View file

@ -5,6 +5,7 @@
#if !UCONFIG_NO_FORMATTING
#include "unicode/measunit.h"
#include "unicode/numberformatter.h"
#include "number_utypes.h"
#include "util.h"
@ -19,6 +20,8 @@ UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedNumber)
#define UPRV_NOARG
// TODO(units,hugovdm): see if there's Unit Usage Formatting consequences here?
// Ensure tests are thorough, check rounding etc.
void FormattedNumber::toDecimalNumber(ByteSink& sink, UErrorCode& status) const {
UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
impl::DecNum decnum;
@ -32,6 +35,11 @@ void FormattedNumber::getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpi
fData->getAllFieldPositions(fpih, status);
}
MeasureUnit FormattedNumber::getOutputUnit(UErrorCode& status) const {
UPRV_FORMATTED_VALUE_METHOD_GUARD(MeasureUnit())
return fData->outputUnit;
}
void FormattedNumber::getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const {
UPRV_FORMATTED_VALUE_METHOD_GUARD(UPRV_NOARG)
output = fData->quantity;

View file

@ -246,16 +246,17 @@ class U_I18N_API ModifierStore {
* itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
* quantity to generate a finalized {@link MicroProps}, which can be used to render the number to output.
*
* <p>
* In other words, this interface is used for the parts of number processing that are <em>quantity-dependent</em>.
*
* <p>
* In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of MicroPropsGenerators
* are linked together, and each one is responsible for manipulating a certain quantity-dependent part of the
* MicroProps. At the tail of the linked list is a base instance of {@link MicroProps} with properties that are not
* quantity-dependent. Each element in the linked list calls {@link #processQuantity} on its "parent", then does its
* work, and then returns the result.
*
* This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
* constructing a NumberFormatter.
*
* Exported as U_I18N_API because it is a base class for other exported types
*
*/
@ -264,13 +265,12 @@ class U_I18N_API MicroPropsGenerator {
virtual ~MicroPropsGenerator();
/**
* Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
* Considers the given {@link DecimalQuantity}, optionally mutates it, and
* populates a {@link MicroProps} instance.
*
* @param quantity
* The quantity for consideration and optional mutation.
* @param micros
* The MicroProps instance to populate.
* @return A MicroProps instance resolved for the quantity.
* @param quantity The quantity for consideration and optional mutation.
* @param micros The MicroProps instance to populate. It will be modified as
* needed for the given quantity.
*/
virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros,
UErrorCode& status) const = 0;

View file

@ -0,0 +1,113 @@
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#include "number_usageprefs.h"
#include "number_decimalquantity.h"
#include "number_microprops.h"
#include "unicode/numberformatter.h"
using namespace icu::number;
using namespace icu::number::impl;
// Copy constructor
Usage::Usage(const Usage &other) : fUsage(nullptr), fLength(other.fLength), fError(other.fError) {
if (other.fUsage != nullptr) {
fUsage = (char *)uprv_malloc(fLength + 1);
uprv_strncpy(fUsage, other.fUsage, fLength + 1);
}
}
// Copy assignment operator
Usage &Usage::operator=(const Usage &other) {
fLength = other.fLength;
if (other.fUsage != nullptr) {
fUsage = (char *)uprv_malloc(fLength + 1);
uprv_strncpy(fUsage, other.fUsage, fLength + 1);
}
fError = other.fError;
return *this;
}
// Move constructor - can it be improved by taking over src's "this" instead of
// copying contents? Swapping pointers makes sense for heap objects but not for
// stack objects.
// *this = std::move(src);
Usage::Usage(Usage &&src) U_NOEXCEPT : fUsage(src.fUsage), fLength(src.fLength), fError(src.fError) {
// Take ownership away from src if necessary
src.fUsage = nullptr;
}
// Move assignment operator
Usage &Usage::operator=(Usage &&src) U_NOEXCEPT {
if (this == &src) {
return *this;
}
if (fUsage != nullptr) {
uprv_free(fUsage);
}
fUsage = src.fUsage;
fLength = src.fLength;
fError = src.fError;
// Take ownership away from src if necessary
src.fUsage = nullptr;
return *this;
}
Usage::~Usage() {
if (fUsage != nullptr) {
uprv_free(fUsage);
fUsage = nullptr;
}
}
void Usage::set(StringPiece value) {
if (fUsage != nullptr) {
uprv_free(fUsage);
fUsage = nullptr;
}
fLength = value.length();
fUsage = (char *)uprv_malloc(fLength + 1);
uprv_strncpy(fUsage, value.data(), fLength);
fUsage[fLength] = 0;
}
UsagePrefsHandler::UsagePrefsHandler(const Locale &locale,
const MeasureUnit inputUnit,
const StringPiece usage,
const MicroPropsGenerator *parent,
UErrorCode &status)
: fUnitsRouter(inputUnit, StringPiece(locale.getCountry()), usage, status),
fParent(parent) {
}
void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const {
fParent->processQuantity(quantity, micros, status);
if (U_FAILURE(status)) {
return;
}
quantity.roundToInfinity(); // Enables toDouble
auto routed = fUnitsRouter.route(quantity.toDouble(), status);
micros.outputUnit = routed[0]->getUnit();
quantity.setToDouble(routed[0]->getNumber().getDouble());
// TODO(units): here we are always overriding Precision. (1) get precision
// from fUnitsRouter, (2) ensure we use the UnitPreference skeleton's
// precision only when there isn't an explicit override we prefer to use.
// This needs to be handled within
// NumberFormatterImpl::macrosToMicroGenerator in number_formatimpl.cpp
Precision precision = Precision::integer().withMinDigits(2);
UNumberFormatRoundingMode roundingMode;
// Temporary until ICU 64?
roundingMode = precision.fRoundingMode;
CurrencyUnit currency(u"", status);
micros.rounder = {precision, roundingMode, currency, status};
if (U_FAILURE(status)) {
return;
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,53 @@
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_USAGEPREFS_H__
#define __NUMBER_USAGEPREFS_H__
#include "cmemory.h"
#include "number_types.h"
#include "unitsrouter.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
/**
* A MicroPropsGenerator which uses UnitsRouter to produce output converted to a
* MeasureUnit appropriate for a particular localized usage: see
* NumberFormatterSettings::usage().
*/
class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory {
public:
UsagePrefsHandler(const Locale &locale, const MeasureUnit inputUnit, const StringPiece usage,
const MicroPropsGenerator *parent, UErrorCode &status);
/**
* Obtains the appropriate output value, MeasurementUnit and
* rounding/precision behaviour from the UnitsRouter.
*/
void processQuantity(DecimalQuantity &quantity, MicroProps &micros,
UErrorCode &status) const U_OVERRIDE;
/**
* Returns the list of possible output units, i.e. the full set of
* preferences, for the localized, usage-specific unit preferences.
*
* The returned pointer should be valid for the lifetime of the
* UsagePrefsHandler instance.
*/
const MaybeStackVector<MeasureUnit> *getOutputUnits() const {
return fUnitsRouter.getOutputUnits();
}
private:
UnitsRouter fUnitsRouter;
const MicroPropsGenerator *fParent;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif // __NUMBER_USAGEPREFS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -28,9 +28,6 @@ const DecimalQuantity* validateUFormattedNumberToDecimalQuantity(
* This struct is held internally by the C++ version FormattedNumber since the member types are not
* declared in the public header file.
*
* The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used
* to add a toDecNumber() or similar method.
*
* Exported as U_I18N_API for tests
*/
class U_I18N_API UFormattedNumberData : public FormattedValueStringBuilderImpl {
@ -38,7 +35,13 @@ public:
UFormattedNumberData() : FormattedValueStringBuilderImpl(kUndefinedField) {}
virtual ~UFormattedNumberData();
// The formatted quantity.
DecimalQuantity quantity;
// The output unit for the formatted quantity.
// TODO(units,hugovdm): populate this correctly - it's not properly
// implemented yet.
MeasureUnit outputUnit;
};

View file

@ -123,6 +123,7 @@ number_patternstring.cpp
number_rounding.cpp
number_scientific.cpp
number_skeletons.cpp
number_usageprefs.cpp
number_utils.cpp
numfmt.cpp
numparse_affixes.cpp

View file

@ -158,6 +158,7 @@ struct UFormattedNumberImpl;
class MutablePatternModifier;
class ImmutablePatternModifier;
struct DecimalFormatWarehouse;
class UsagePrefsHandler;
/**
* Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
@ -767,6 +768,11 @@ class U_I18N_API Precision : public UMemory {
// To allow access to the skeleton generation code:
friend class impl::GeneratorHelpers;
// TODO(units): revisit when UnitsRouter is changed: do we still need this
// once Precision is returned by UnitsRouter? For now, we allow access to
// Precision constructor from UsagePrefsHandler:
friend class impl::UsagePrefsHandler;
};
/**
@ -1127,6 +1133,56 @@ class U_I18N_API Scale : public UMemory {
namespace impl {
// Do not enclose entire Usage with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
/**
* Manages NumberFormatterSettings::usage()'s char* instance on the heap.
* @internal
*/
class U_I18N_API Usage : public UMemory {
#ifndef U_HIDE_INTERNAL_API
public:
/** @internal */
Usage(const Usage& other);
/** @internal */
Usage& operator=(const Usage& other);
/** @internal */
Usage(Usage &&src) U_NOEXCEPT;
/** @internal */
Usage& operator=(Usage&& src) U_NOEXCEPT;
/** @internal */
~Usage();
/** @internal */
int16_t length() const { return fLength; }
/** @internal */
void set(StringPiece value);
/** @internal */
bool isSet() const { return fLength > 0; }
private:
char *fUsage;
int16_t fLength;
UErrorCode fError;
Usage() : fUsage(nullptr), fLength(0), fError(U_ZERO_ERROR) {}
// Allow NumberFormatterImpl to access fUsage.
friend class impl::NumberFormatterImpl;
// Allow MacroProps/MicroProps to initialize empty instances.
friend struct impl::MacroProps;
#endif // U_HIDE_INTERNAL_API
};
// Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
/** @internal */
class U_I18N_API SymbolsWrapper : public UMemory {
@ -1410,6 +1466,9 @@ struct U_I18N_API MacroProps : public UMemory {
/** @internal */
Scale scale; // = Scale(); (benign value)
/** @internal */
Usage usage; // = Usage(); (no usage)
/** @internal */
const AffixPatternProvider* affixProvider = nullptr; // no ownership
@ -1599,6 +1658,9 @@ class U_I18N_API NumberFormatterSettings {
*
* If a per-unit is specified without a primary unit via {@link #unit}, the behavior is undefined.
*
* TODO(units): add proper support for COMPOUND and MIXED units.
* Specify behaviour here, test intended behaviour...
*
* @param perUnit
* The unit to render in the denominator.
* @return The fluent chain
@ -1610,6 +1672,9 @@ class U_I18N_API NumberFormatterSettings {
/**
* Overload of perUnit() for use on an rvalue reference.
*
* TODO(units): add proper support for COMPOUND and MIXED units.
* Specify behaviour here, test intended behaviour...
*
* @param perUnit
* The unit to render in the denominator.
* @return The fluent chain.
@ -1624,6 +1689,9 @@ class U_I18N_API NumberFormatterSettings {
*
* Note: consider using the MeasureFormat factory methods that return by value.
*
* TODO(units): add proper support for COMPOUND and MIXED units.
* Specify behaviour here, test intended behaviour...
*
* @param perUnit
* The unit to render in the denominator.
* @return The fluent chain.
@ -1636,6 +1704,9 @@ class U_I18N_API NumberFormatterSettings {
/**
* Overload of adoptPerUnit() for use on an rvalue reference.
*
* TODO(units): add proper support for COMPOUND and MIXED units.
* Specify behaviour here, test intended behaviour...
*
* @param perUnit
* The unit to render in the denominator.
* @return The fluent chain.
@ -2073,6 +2144,15 @@ class U_I18N_API NumberFormatterSettings {
* Setting usage to an empty string clears the usage (disables usage-based
* localized formatting).
*
* Setting a usage string but not a correct input unit will result in an
* U_ILLEGAL_ARGUMENT_ERROR.
*
* TODO(units): When setting both usage and rounding/precision behaviour via
* NumberFormatterSetter, we think we want the latter to override any
* skeleton in the UnitPreferences. Add unit tests to demontrate desired
* behaviour, fix macrosToMicroGenerator to handle this correctly, and
* update this documentation.
*
* @param usage A `usage` parameter from the units resource. See the
* unitPreferenceData in *source/data/misc/units.txt*, generated from
* `unitPreferenceData` in [CLDR's

View file

@ -410,7 +410,7 @@ void U_I18N_API UnitPreferences::getPreferencesFor(StringPiece category, StringP
if (U_FAILURE(status)) { return; }
U_ASSERT(idx >= 0); // Failures should have been taken care of by `status`.
const UnitPreferenceMetadata *m = metadata_[idx];
outPreferences = unitPrefs_.getConstAlias() + m->prefsOffset;
outPreferences = unitPrefs_.getAlias() + m->prefsOffset;
preferenceCount = m->prefsCount;
}

View file

@ -41,6 +41,7 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
return;
}
outputUnits_.emplaceBackAndCheckErrorCode(status, complexTargetUnit);
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit,
preference.geq, conversionRates, status);
if (U_FAILURE(status)) {
@ -49,7 +50,7 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece
}
}
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) {
MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status) const {
for (int i = 0, n = converterPreferences_.length(); i < n; i++) {
const auto &converterPreference = *converterPreferences_[i];
@ -63,6 +64,10 @@ MaybeStackVector<Measure> UnitsRouter::route(double quantity, UErrorCode &status
return lastConverter.convert(quantity, status);
}
const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
return &outputUnits_;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -75,9 +75,21 @@ class U_I18N_API UnitsRouter {
public:
UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
MaybeStackVector<Measure> route(double quantity, UErrorCode &status);
MaybeStackVector<Measure> route(double quantity, UErrorCode &status) const;
/**
* Returns the list of possible output units, i.e. the full set of
* preferences, for the localized, usage-specific unit preferences.
*
* The returned pointer should be valid for the lifetime of the
* UnitsRouter instance.
*/
const MaybeStackVector<MeasureUnit> *getOutputUnits() const;
private:
// List of possible output units
MaybeStackVector<MeasureUnit> outputUnits_;
MaybeStackVector<ConverterPreference> converterPreferences_;
};

View file

@ -979,6 +979,8 @@ group: number_output
number_representation format formatted_value_sbimpl
# PluralRules internals:
unifiedcache
# Unit Formatting
units
group: numberformatter
# ICU 60+ NumberFormatter API
@ -990,11 +992,11 @@ group: numberformatter
number_mapper.o number_modifiers.o number_multiplier.o
number_notation.o number_padding.o
number_patternmodifier.o number_patternstring.o number_rounding.o
number_scientific.o
number_scientific.o number_usageprefs.o
currpinf.o dcfmtsym.o numsys.o
numrange_fluent.o numrange_impl.o
deps
decnumber double_conversion formattable units
decnumber double_conversion formattable units unitsformatter
number_representation number_output
uclean_i18n common

View file

@ -35,6 +35,10 @@ typedef struct UFieldPositionWithCategory {
class IntlTestWithFieldPosition : public IntlTest {
public:
// Tests FormattedValue's toString, toTempString, and nextPosition methods.
//
// expectedCategory gets combined with expectedFieldPositions to call
// checkMixedFormattedValue.
void checkFormattedValue(
const char16_t* message,
const FormattedValue& fv,
@ -43,6 +47,7 @@ public:
const UFieldPosition* expectedFieldPositions,
int32_t length);
// Tests FormattedValue's toString, toTempString, and nextPosition methods.
void checkMixedFormattedValue(
const char16_t* message,
const FormattedValue& fv,

View file

@ -54,6 +54,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void notationCompact();
void unitMeasure();
void unitCompoundMeasure();
void unitUsage();
void unitCurrency();
void unitPercent();
void percentParity();

View file

@ -73,6 +73,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(notationCompact);
TESTCASE_AUTO(unitMeasure);
TESTCASE_AUTO(unitCompoundMeasure);
TESTCASE_AUTO(unitUsage);
TESTCASE_AUTO(unitCurrency);
TESTCASE_AUTO(unitPercent);
if (!quick) {
@ -674,6 +675,87 @@ void NumberFormatterApiTest::unitMeasure() {
u"5 a\u00F1os");
}
void NumberFormatterApiTest::unitUsage() {
UnlocalizedNumberFormatter unloc_formatter =
NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
IcuTestErrorCode status(*this, "unitUsage()");
LocalizedNumberFormatter formatter = unloc_formatter.locale("en-ZA");
FormattedNumber formattedNum = formatter.formatDouble(300, status);
assertTrue(UnicodeString("unitUsage() en-ZA road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-ZA road", "300 m", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-ZA road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-ZA"),
// u"87\u00A0650 km",
// u"8\u00A0765 km",
// u"877 km",
// u"88 km",
// u"8,8 km",
// u"877 m",
// u"88 m",
// u"8,8 m",
// u"0 m");
formatter = unloc_formatter.locale("en-GB");
formattedNum = formatter.formatDouble(300, status);
assertTrue(UnicodeString("unitUsage() en-GB road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getYard() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-GB road", "328 yd", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-GB road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-GB"),
// u"54,463 mi",
// u"5,446 mi",
// u"545 mi",
// u"54 mi",
// u"5.4 mi",
// u"0.54 mi",
// u"96 yd",
// u"9.6 yd",
// u"0 yd");
formatter = unloc_formatter.locale("en-US");
formattedNum = formatter.formatDouble(300, status);
assertTrue(UnicodeString("unitUsage() en-US road, got outputUnit: \"") +
formattedNum.getOutputUnit(status).getIdentifier() + "\"",
MeasureUnit::getFoot() == formattedNum.getOutputUnit(status));
assertEquals("unitUsage() en-US road", "984 ft", formattedNum.toString(status));
// TODO(units,hugovdm): this works with "assertUndefinedSkeleton(f);"
// commented out. Design skeletons ("measure-unit/length-meter usage/road"?)
// then use this:
// assertFormatDescendingBig(u"unitUsage() en-US road",
// nullptr,
// nullptr,
// unloc_formatter,
// Locale("en-US"),
// u"54,463 mi",
// u"5,446 mi",
// u"545 mi",
// u"54 mi",
// u"5.4 mi",
// u"0.54 mi",
// u"288 ft",
// u"29 ft",
// u"0 ft");
// TODO(hugovdm): consider fixing TODO(ICU-20941) too?
}
void NumberFormatterApiTest::unitCompoundMeasure() {
assertFormatDescending(
u"Meters Per Second Short (unit that simplifies) and perUnit method",