mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 06:25:30 +00:00
Merge pull request #52 from icu-units/mixedunits
ICU-20941 Support Mixed Units in NumberFormatter when using usage()
This commit is contained in:
commit
cc5a122021
15 changed files with 657 additions and 68 deletions
|
@ -383,9 +383,12 @@ public:
|
|||
* caller becomes responsible for deleting the array
|
||||
*/
|
||||
inline T *orphanOrClone(int32_t length, int32_t &resultCapacity);
|
||||
private:
|
||||
|
||||
protected: // TODO(icu-units#64): make these private again if possible?
|
||||
T *ptr;
|
||||
int32_t capacity;
|
||||
|
||||
private:
|
||||
UBool needToRelease;
|
||||
T stackArray[stackCapacity];
|
||||
void releaseArray() {
|
||||
|
|
|
@ -604,9 +604,9 @@ static const char * const gSubTypes[] = {
|
|||
static int32_t unitPerUnitToSingleUnit[][4] = {
|
||||
{378, 382, 12, 5},
|
||||
{378, 387, 12, 6},
|
||||
{388, 343, 19, 0},
|
||||
{390, 350, 19, 2},
|
||||
{392, 343, 19, 3},
|
||||
{388, 343, 19, 0}, // kilometer per hour
|
||||
{390, 350, 19, 2}, // meter per second
|
||||
{392, 343, 19, 3}, // mile per hour
|
||||
{392, 463, 4, 2},
|
||||
{392, 464, 4, 3},
|
||||
{411, 460, 3, 1},
|
||||
|
|
|
@ -34,6 +34,7 @@ constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
|
|||
* @copydoc DNAM_INDEX
|
||||
*/
|
||||
constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
|
||||
// Number of keys in the array populated by PluralTableSink.
|
||||
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
|
||||
|
||||
static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
|
||||
|
@ -48,6 +49,11 @@ static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
|
|||
}
|
||||
}
|
||||
|
||||
// Selects a string out of the `strings` array which corresponds to the
|
||||
// specified plural form, with fallback to the OTHER form.
|
||||
//
|
||||
// The `strings` array must have ARRAY_LENGTH items: one corresponding to each
|
||||
// of the plural forms, plus a display name ("dnam") and a "per" form.
|
||||
static UnicodeString getWithPlural(
|
||||
const UnicodeString* strings,
|
||||
StandardPlural::Form plural,
|
||||
|
@ -97,12 +103,18 @@ 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.
|
||||
/**
|
||||
* Populates outArray with `locale`-specific values for `unit` through use of
|
||||
* PluralTableSink. Only the set of basic units are supported!
|
||||
*
|
||||
* 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 reads just "units".
|
||||
*
|
||||
* @param unit must have a type and subtype (i.e. it must be a unit listed in
|
||||
* gTypes and gSubTypes in measunit.cpp).
|
||||
* @param 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);
|
||||
|
@ -200,24 +212,26 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
|
|||
|
||||
} // namespace
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Not valid for mixed units that aren't built-in units, and there should
|
||||
// not be any built-in mixed units!
|
||||
U_ASSERT(uprv_strlen(unitRef.getType()) > 0 || unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(fillIn != nullptr);
|
||||
if (uprv_strlen(unitRef.getType()) == 0 || uprv_strlen(perUnit.getType()) == 0) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an error code.
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
|
||||
// error code. Once we support not-built-in units here, unitRef may be
|
||||
// anything, but if not built-in, perUnit has to be "none".
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
MeasureUnit unit = unitRef;
|
||||
if (uprv_strcmp(perUnit.getType(), "none") != 0) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
// Compound unit: first try to simplify (e.g. "meter per second" is a
|
||||
// built-in unit).
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolved = MeasureUnit::resolveUnitPerUnit(unit, perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
|
@ -240,7 +254,6 @@ void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitR
|
|||
status);
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -386,6 +399,125 @@ const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural::
|
|||
return &fModifiers[plural];
|
||||
}
|
||||
|
||||
void MixedUnitLongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent,
|
||||
MixedUnitLongNameHandler *fillIn, UErrorCode &status) {
|
||||
U_ASSERT(mixedUnit.getComplexity(status) == UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(fillIn != nullptr);
|
||||
|
||||
LocalArray<MeasureUnit> individualUnits =
|
||||
mixedUnit.splitToSingleUnits(fillIn->fMixedUnitCount, status);
|
||||
fillIn->fMixedUnitData.adoptInstead(new UnicodeString[fillIn->fMixedUnitCount * ARRAY_LENGTH]);
|
||||
for (int32_t i = 0; i < fillIn->fMixedUnitCount; i++) {
|
||||
// Grab data for each of the components.
|
||||
UnicodeString *unitData = &fillIn->fMixedUnitData[i * ARRAY_LENGTH];
|
||||
getMeasureData(loc, individualUnits[i], width, unitData, status);
|
||||
}
|
||||
|
||||
fillIn->fListFormatter.adoptInsteadAndCheckErrorCode(ListFormatter::createInstance(loc, status),
|
||||
status);
|
||||
fillIn->rules = rules;
|
||||
fillIn->parent = parent;
|
||||
|
||||
// We need a localised NumberFormatter for the integers of the bigger units
|
||||
// (providing Arabic numerals, for example).
|
||||
fillIn->fIntegerFormatter = NumberFormatter::withLocale(loc);
|
||||
}
|
||||
|
||||
void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
U_ASSERT(fMixedUnitCount > 1);
|
||||
if (parent != nullptr) {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
}
|
||||
micros.modOuter = getMixedUnitModifier(quantity, micros, status);
|
||||
}
|
||||
|
||||
const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
|
||||
MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
// TODO(icu-units#21): mixed units without usage() is not yet supported.
|
||||
// That should be the only reason why this happens, so delete this whole if
|
||||
// once fixed:
|
||||
if (micros.mixedMeasuresCount == 0) {
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return µs.helpers.emptyWeakModifier;
|
||||
}
|
||||
U_ASSERT(micros.mixedMeasuresCount > 0);
|
||||
// mixedMeasures does not contain the last value:
|
||||
U_ASSERT(fMixedUnitCount == micros.mixedMeasuresCount + 1);
|
||||
U_ASSERT(fListFormatter.isValid());
|
||||
|
||||
// Algorithm:
|
||||
//
|
||||
// For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should
|
||||
// find "3 yard" and "1 foot" in micros.mixedMeasures.
|
||||
//
|
||||
// Obtain long-names with plural forms corresponding to measure values:
|
||||
// * {0} yards, {0} foot, {0} inches
|
||||
//
|
||||
// Format the integer values appropriately and modify with the format
|
||||
// strings:
|
||||
// - 3 yards, 1 foot
|
||||
//
|
||||
// Use ListFormatter to combine, with one placeholder:
|
||||
// - 3 yards, 1 foot and {0} inches
|
||||
//
|
||||
// Return a SimpleModifier for this pattern, letting the rest of the
|
||||
// pipeline take care of the remaining inches.
|
||||
|
||||
LocalArray<UnicodeString> outputMeasuresList(new UnicodeString[fMixedUnitCount], status);
|
||||
if (U_FAILURE(status)) {
|
||||
return µs.helpers.emptyWeakModifier;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < micros.mixedMeasuresCount; i++) {
|
||||
DecimalQuantity fdec;
|
||||
fdec.setToLong(micros.mixedMeasures[i]);
|
||||
StandardPlural::Form pluralForm = utils::getStandardPlural(rules, fdec);
|
||||
|
||||
UnicodeString simpleFormat =
|
||||
getWithPlural(&fMixedUnitData[i * ARRAY_LENGTH], pluralForm, status);
|
||||
SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
|
||||
|
||||
UnicodeString num;
|
||||
auto appendable = UnicodeStringAppendable(num);
|
||||
fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
|
||||
compiledFormatter.format(num, outputMeasuresList[i], status);
|
||||
}
|
||||
|
||||
UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
|
||||
StandardPlural::Form finalPlural = utils::getPluralSafe(micros.rounder, rules, quantity, status);
|
||||
UnicodeString finalSimpleFormat = getWithPlural(finalSimpleFormats, finalPlural, status);
|
||||
SimpleFormatter finalFormatter(finalSimpleFormat, 0, 1, status);
|
||||
finalFormatter.format(UnicodeString(u"{0}"), outputMeasuresList[fMixedUnitCount - 1], status);
|
||||
|
||||
// Combine list into a "premixed" pattern
|
||||
UnicodeString premixedFormatPattern;
|
||||
fListFormatter->format(outputMeasuresList.getAlias(), fMixedUnitCount, premixedFormatPattern,
|
||||
status);
|
||||
SimpleFormatter premixedCompiled(premixedFormatPattern, 0, 1, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return µs.helpers.emptyWeakModifier;
|
||||
}
|
||||
|
||||
// Return a SimpleModifier for the "premixed" pattern
|
||||
micros.helpers.mixedUnitModifier =
|
||||
SimpleModifier(premixedCompiled, {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD}, false,
|
||||
{this, SIGNUM_POS_ZERO, finalPlural});
|
||||
return µs.helpers.mixedUnitModifier;
|
||||
}
|
||||
|
||||
const Modifier *MixedUnitLongNameHandler::getModifier(Signum /*signum*/,
|
||||
StandardPlural::Form /*plural*/) const {
|
||||
// TODO(units): investigate this method when investigating where
|
||||
// LongNameHandler::getModifier() gets used. To be sure it remains
|
||||
// unreachable:
|
||||
UPRV_UNREACHABLE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LongNameMultiplexer *
|
||||
LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<MeasureUnit> &units,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
|
@ -395,16 +527,23 @@ LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<M
|
|||
return nullptr;
|
||||
}
|
||||
U_ASSERT(units.length() > 0);
|
||||
if (result->fHandlers.resize(units.length()) == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
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);
|
||||
const MeasureUnit& unit = *units[i];
|
||||
result->fMeasureUnits[i] = unit;
|
||||
if (unit.getComplexity(status) == UMEASURE_UNIT_MIXED) {
|
||||
MixedUnitLongNameHandler *mlnh = result->fMixedUnitHandlers.createAndCheckErrorCode(status);
|
||||
MixedUnitLongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, mlnh, status);
|
||||
result->fHandlers[i] = mlnh;
|
||||
} else {
|
||||
LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
|
||||
LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
|
||||
result->fHandlers[i] = lnh;
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -420,9 +559,9 @@ void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps
|
|||
fParent->processQuantity(quantity, micros, status);
|
||||
|
||||
// Call the correct LongNameHandler based on outputUnit
|
||||
for (int i = 0; i < fLongNameHandlers.length(); i++) {
|
||||
for (int i = 0; i < fHandlers.getCapacity(); i++) {
|
||||
if (fMeasureUnits[i] == micros.outputUnit) {
|
||||
fLongNameHandlers[i]->processQuantity(quantity, micros, status);
|
||||
fHandlers[i]->processQuantity(quantity, micros, status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define __NUMBER_LONGNAMES_H__
|
||||
|
||||
#include "cmemory.h"
|
||||
#include "unicode/listformatter.h"
|
||||
#include "unicode/uversion.h"
|
||||
#include "number_utils.h"
|
||||
#include "number_modifiers.h"
|
||||
|
@ -34,17 +35,47 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
|||
forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Construct a localized LongNameHandler for the specified MeasureUnit.
|
||||
*
|
||||
* Compound units can be constructed via `unit` and `perUnit`. Both of these
|
||||
* must then be built-in units.
|
||||
*
|
||||
* Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit.
|
||||
*
|
||||
* This function uses a fillIn intead of returning a pointer, because we
|
||||
* want to fill in instances in a MemoryPool (which cannot adopt pointers it
|
||||
* didn't create itself).
|
||||
*
|
||||
* @param loc The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for. If
|
||||
* `perUnit` is also defined, `unit` must not be a mixed unit.
|
||||
* @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Does not take ownership.
|
||||
* @param parent Does not take ownership.
|
||||
* @param fillIn Required.
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Selects the plural-appropriate Modifier from the set of fModifiers based
|
||||
* on the plural form.
|
||||
*/
|
||||
void
|
||||
processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
// TODO(units): investigate whether we might run into Mixed Unit trouble
|
||||
// with this. This override for ModifierStore::getModifier does not support
|
||||
// mixed units: investigate under which circumstances it gets called (check
|
||||
// both ImmutablePatternModifier and in NumberRangeFormatterImpl).
|
||||
const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
// A set of pre-computed modifiers, one for each plural form.
|
||||
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
|
||||
// Not owned
|
||||
const PluralRules *rules;
|
||||
|
@ -58,34 +89,132 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
|||
LongNameHandler() : rules(nullptr), parent(nullptr) {
|
||||
}
|
||||
|
||||
friend class MemoryPool<LongNameHandler>; // To enable emplaceBack();
|
||||
// Enables MemoryPool<LongNameHandler>::emplaceBack(): requires access to
|
||||
// the private constructors.
|
||||
friend class MemoryPool<LongNameHandler>;
|
||||
|
||||
friend class NumberFormatterImpl;
|
||||
|
||||
// Fills in LongNameHandler fields for formatting compound units identified
|
||||
// via `unit` and `perUnit`. Both `unit` and `perUnit` need to be built-in
|
||||
// units (for which data exists).
|
||||
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);
|
||||
|
||||
// Sets fModifiers to use the patterns from `simpleFormats`.
|
||||
void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status);
|
||||
|
||||
// Sets fModifiers to a combination of `leadFormats` (one per plural form)
|
||||
// and `trailFormat` appended to each.
|
||||
//
|
||||
// With a leadFormat of "{0}m" and a trailFormat of "{0}/s", it produces a
|
||||
// pattern of "{0}m/s" by inserting the leadFormat pattern into trailFormat.
|
||||
void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, UErrorCode &status);
|
||||
};
|
||||
|
||||
const int MAX_PREFS_COUNT = 10;
|
||||
// Similar to LongNameHandler, but only for MIXED units.
|
||||
class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStore, public UMemory {
|
||||
public:
|
||||
/**
|
||||
* Construct a localized MixedUnitLongNameHandler for the specified
|
||||
* MeasureUnit. It must be a MIXED unit.
|
||||
*
|
||||
* This function uses a fillIn intead of returning a pointer, because we
|
||||
* want to fill in instances in a MemoryPool (which cannot adopt pointers it
|
||||
* didn't create itself).
|
||||
*
|
||||
* @param loc The desired locale.
|
||||
* @param mixedUnit The mixed measure unit to construct a
|
||||
* MixedUnitLongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Does not take ownership.
|
||||
* @param parent Does not take ownership.
|
||||
* @param fillIn Required.
|
||||
*/
|
||||
static void forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, MixedUnitLongNameHandler *fillIn,
|
||||
UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
|
||||
* taken as the final smallest unit, while the larger unit values must be
|
||||
* provided via `micros.mixedMeasures`.
|
||||
*/
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
// Required for ModifierStore. And ModifierStore is required by
|
||||
// SimpleModifier constructor's last parameter. We assert his will never get
|
||||
// called though.
|
||||
const Modifier *getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
// Not owned
|
||||
const PluralRules *rules;
|
||||
// Not owned
|
||||
const MicroPropsGenerator *parent;
|
||||
|
||||
// Total number of units in the MeasureUnit this LongNameHandler was
|
||||
// configured for: for "foot-and-inch", this will be 2. (If not a mixed unit,
|
||||
// this will be 1.)
|
||||
int32_t fMixedUnitCount = 1;
|
||||
// If this LongNameHandler is for a mixed unit, this stores unit data for
|
||||
// each of the individual units. For each unit, it stores ARRAY_LENGTH
|
||||
// strings, as returned by getMeasureData. (Each unit with index `i` has
|
||||
// ARRAY_LENGTH strings starting at index `i*ARRAY_LENGTH` in this array.)
|
||||
LocalArray<UnicodeString> fMixedUnitData;
|
||||
// A localized NumberFormatter used to format the integer-valued bigger
|
||||
// units of Mixed Unit measurements.
|
||||
LocalizedNumberFormatter fIntegerFormatter;
|
||||
// A localised list formatter for joining mixed units together.
|
||||
LocalPointer<ListFormatter> fListFormatter;
|
||||
|
||||
MixedUnitLongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
|
||||
: rules(rules), parent(parent) {
|
||||
}
|
||||
|
||||
MixedUnitLongNameHandler() : rules(nullptr), parent(nullptr) {
|
||||
}
|
||||
|
||||
// Enables MemoryPool<LongNameHandler>::emplaceBack(): requires access to
|
||||
// the private constructors.
|
||||
friend class MemoryPool<MixedUnitLongNameHandler>;
|
||||
|
||||
// Fills in LongNameHandler fields for formatting mixed units. Each unit in
|
||||
// a mixed unit must be a built-in unit.
|
||||
static void forMixedUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
MixedUnitLongNameHandler *fillIn, UErrorCode &status);
|
||||
|
||||
// For a mixed unit, returns a Modifier that takes only one parameter: the
|
||||
// smallest and final unit of the set. The bigger units' values and labels
|
||||
// get baked into this Modifier, together with the unit label of the final
|
||||
// unit.
|
||||
const Modifier *getMixedUnitModifier(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A MicroPropsGenerator that multiplexes between different LongNameHandlers,
|
||||
* depending on the outputUnit (micros.helpers.outputUnit should be set earlier
|
||||
* in the chain).
|
||||
* depending on the outputUnit.
|
||||
*
|
||||
* See processQuantity() for the input requirements.
|
||||
*/
|
||||
class LongNameMultiplexer : public MicroPropsGenerator, public UMemory {
|
||||
public:
|
||||
// FIXME: docstring?
|
||||
// Produces a multiplexer for LongNameHandlers, one for each unit in
|
||||
// `units`. An individual unit might be a mixed unit.
|
||||
static LongNameMultiplexer *forMeasureUnits(const Locale &loc,
|
||||
const MaybeStackVector<MeasureUnit> &units,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
// The output unit must be provided via `micros.outputUnit`, it must match
|
||||
// one of the units provided to the factory function.
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
|
@ -95,8 +224,14 @@ class LongNameMultiplexer : public MicroPropsGenerator, public UMemory {
|
|||
* earlier MicroPropsGenerators in the chain, LongNameMultiplexer keeps the
|
||||
* parent link, while the LongNameHandlers are given no parents.
|
||||
*/
|
||||
MaybeStackVector<LongNameHandler> fLongNameHandlers;
|
||||
MemoryPool<LongNameHandler> fLongNameHandlers;
|
||||
MemoryPool<MixedUnitLongNameHandler> fMixedUnitHandlers;
|
||||
// Unowned pointers to instances owned by MaybeStackVectors.
|
||||
MaybeStackArray<MicroPropsGenerator *, 8> fHandlers;
|
||||
// Each MeasureUnit corresponds to the same-index MicroPropsGenerator
|
||||
// pointed to in fHandlers.
|
||||
LocalArray<MeasureUnit> fMeasureUnits;
|
||||
|
||||
const MicroPropsGenerator *fParent;
|
||||
|
||||
LongNameMultiplexer(const MicroPropsGenerator *parent) : fParent(parent) {
|
||||
|
|
|
@ -22,6 +22,57 @@
|
|||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
||||
/**
|
||||
* A copyable container for the integer values of mixed unit measurements.
|
||||
*
|
||||
* If memory allocation fails during copying, no values are copied and status is
|
||||
* set to U_MEMORY_ALLOCATION_ERROR.
|
||||
*/
|
||||
class IntMeasures : public MaybeStackArray<int64_t, 2> {
|
||||
public:
|
||||
/**
|
||||
* Default constructor initializes with internal T[stackCapacity] buffer.
|
||||
*
|
||||
* Stack Capacity: most mixed units are expected to consist of two or three
|
||||
* subunits, so one or two integer measures should be enough.
|
||||
*/
|
||||
IntMeasures() : MaybeStackArray<int64_t, 2>() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*
|
||||
* If memory allocation fails during copying, no values are copied and
|
||||
* status is set to U_MEMORY_ALLOCATION_ERROR.
|
||||
*/
|
||||
IntMeasures(const IntMeasures &other) {
|
||||
this->operator=(other);
|
||||
};
|
||||
|
||||
// Assignment operator
|
||||
IntMeasures &operator=(const IntMeasures &rhs) {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
int32_t length = rhs.capacity;
|
||||
if (this->resize(length, 0) != NULL) {
|
||||
U_ASSERT(this->capacity == rhs.capacity);
|
||||
uprv_memcpy(this->ptr, rhs.ptr, (size_t)length * sizeof(int64_t));
|
||||
} else {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
/** Move constructor */
|
||||
IntMeasures(IntMeasures &&src) = default;
|
||||
|
||||
/** Move assignment */
|
||||
IntMeasures &operator=(IntMeasures &&src) = default;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
};
|
||||
|
||||
// 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
|
||||
|
@ -41,22 +92,48 @@ struct MicroProps : public MicroPropsGenerator {
|
|||
|
||||
// Note: This struct has no direct ownership of the following pointers.
|
||||
const DecimalFormatSymbols* symbols;
|
||||
|
||||
// Pointers to Modifiers provided by the number formatting pipeline (when
|
||||
// the value is known):
|
||||
|
||||
// A Modifier provided by LongNameHandler, used for currency long names and
|
||||
// units. If there is no LongNameHandler needed, this should be an
|
||||
// EmptyModifier. (This is typically the third modifier applied.)
|
||||
const Modifier* modOuter;
|
||||
// A Modifier for short currencies and compact notation. (This is typically
|
||||
// the second modifier applied.)
|
||||
const Modifier* modMiddle = nullptr;
|
||||
// A Modifier provided by ScientificHandler, used for scientific notation.
|
||||
// This is typically the first modifier applied.
|
||||
const Modifier* modInner;
|
||||
|
||||
// The following "helper" fields may optionally be used during the MicroPropsGenerator.
|
||||
// They live here to retain memory.
|
||||
struct {
|
||||
// The ScientificModifier for which ScientificHandler is responsible.
|
||||
// ScientificHandler::processQuantity() modifies this Modifier.
|
||||
ScientificModifier scientificModifier;
|
||||
// EmptyModifier used for modOuter
|
||||
EmptyModifier emptyWeakModifier{false};
|
||||
// EmptyModifier used for modInner
|
||||
EmptyModifier emptyStrongModifier{true};
|
||||
MultiplierFormatHandler multiplier;
|
||||
// A Modifier used for Mixed Units. When formatting mixed units,
|
||||
// LongNameHandler assigns this Modifier.
|
||||
SimpleModifier mixedUnitModifier;
|
||||
} helpers;
|
||||
|
||||
// The MeasureUnit with which the output measurement is represented.
|
||||
// The MeasureUnit with which the output is represented. May also have
|
||||
// UMEASURE_UNIT_MIXED complexity, in which case mixedMeasures comes into
|
||||
// play.
|
||||
MeasureUnit outputUnit;
|
||||
|
||||
// In the case of mixed units, this is the set of integer-only units
|
||||
// *preceding* the final unit.
|
||||
IntMeasures mixedMeasures;
|
||||
// Number of mixedMeasures that have been populated
|
||||
int32_t mixedMeasuresCount = 0;
|
||||
|
||||
MicroProps() = default;
|
||||
|
||||
MicroProps(const MicroProps& other) = default;
|
||||
|
|
|
@ -1061,7 +1061,15 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up.
|
||||
// Mixed units can only be represented by a full MeasureUnit instances, so
|
||||
// we ignore macros.perUnit.
|
||||
if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
|
||||
macros.unit = std::move(fullUnit).build(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up (see also
|
||||
// https://github.com/icu-units/icu/issues/35).
|
||||
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
|
||||
SingleUnitImpl* subUnit = fullUnit.units[i];
|
||||
if (subUnit->dimensionality > 0) {
|
||||
|
|
|
@ -246,6 +246,10 @@ void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb
|
|||
|
||||
void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
/**
|
||||
* Parses unit identifiers like "meter-per-second" and "foot-and-inch", as
|
||||
* specified via a "unit/" concise skeleton.
|
||||
*/
|
||||
void parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
void parseUnitUsageOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
|
|
@ -41,10 +41,7 @@ Usage &Usage::operator=(const Usage &other) {
|
|||
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);
|
||||
// Move constructor
|
||||
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;
|
||||
|
@ -105,9 +102,43 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
|
|||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
const auto& routedUnits = routed.measures;
|
||||
micros.outputUnit = routedUnits[0]->getUnit();
|
||||
quantity.setToDouble(routedUnits[0]->getNumber().getDouble());
|
||||
const MaybeStackVector<Measure>& routedUnits = routed.measures;
|
||||
micros.outputUnit = routed.outputUnit.copy(status).build(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
micros.mixedMeasuresCount = routedUnits.length() - 1;
|
||||
if (micros.mixedMeasuresCount > 0) {
|
||||
#ifdef U_DEBUG
|
||||
U_ASSERT(micros.outputUnit.getComplexity(status) == UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(U_SUCCESS(status));
|
||||
// Check that we received measurements with the expected MeasureUnits:
|
||||
int32_t singleUnitsCount;
|
||||
LocalArray<MeasureUnit> singleUnits =
|
||||
micros.outputUnit.splitToSingleUnits(singleUnitsCount, status);
|
||||
U_ASSERT(U_SUCCESS(status));
|
||||
U_ASSERT(routedUnits.length() == singleUnitsCount);
|
||||
for (int32_t i = 0; i < routedUnits.length(); i++) {
|
||||
U_ASSERT(routedUnits[i]->getUnit() == singleUnits[i]);
|
||||
}
|
||||
#endif
|
||||
// Mixed units: except for the last value, we pass all values to the
|
||||
// LongNameHandler via micros.mixedMeasures.
|
||||
if (micros.mixedMeasures.getCapacity() < micros.mixedMeasuresCount) {
|
||||
if (micros.mixedMeasures.resize(micros.mixedMeasuresCount) == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int32_t i = 0; i < micros.mixedMeasuresCount; i++) {
|
||||
micros.mixedMeasures[i] = routedUnits[i]->getNumber().getInt64();
|
||||
}
|
||||
} else {
|
||||
micros.mixedMeasuresCount = 0;
|
||||
}
|
||||
// The last value (potentially the only value) gets passed on via quantity.
|
||||
quantity.setToDouble(routedUnits[routedUnits.length() - 1]->getNumber().getDouble());
|
||||
|
||||
UnicodeString precisionSkeleton = routed.precision;
|
||||
if (micros.rounder.fPrecision.isBogus()) {
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "cmemory.h"
|
||||
#include "number_types.h"
|
||||
#include "unicode/listformatter.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
|
@ -34,6 +36,9 @@ class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory
|
|||
/**
|
||||
* Obtains the appropriate output value, MeasurementUnit and
|
||||
* rounding/precision behaviour from the UnitsRouter.
|
||||
*
|
||||
* The output unit is passed on to the LongNameHandler via
|
||||
* micros.outputUnit.
|
||||
*/
|
||||
void processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
|
|
@ -72,17 +72,17 @@ RouteResult UnitsRouter::route(double quantity, UErrorCode &status) const {
|
|||
const auto &converterPreference = *converterPreferences_[i];
|
||||
|
||||
if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) {
|
||||
return RouteResult(converterPreference.converter.convert(quantity, status), //
|
||||
converterPreference.precision //
|
||||
);
|
||||
return RouteResult(converterPreference.converter.convert(quantity, status),
|
||||
converterPreference.precision,
|
||||
converterPreference.targetUnit.copy(status));
|
||||
}
|
||||
}
|
||||
|
||||
// In case of the `quantity` does not fit in any converter limit, use the last converter.
|
||||
const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]);
|
||||
return RouteResult(lastConverterPreference.converter.convert(quantity, status), //
|
||||
lastConverterPreference.precision //
|
||||
);
|
||||
return RouteResult(lastConverterPreference.converter.convert(quantity, status),
|
||||
lastConverterPreference.precision,
|
||||
lastConverterPreference.targetUnit.copy(status));
|
||||
}
|
||||
|
||||
const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
|
||||
|
|
|
@ -37,8 +37,13 @@ struct RouteResult : UMemory {
|
|||
// or document that other skeleton elements are ignored?
|
||||
UnicodeString precision;
|
||||
|
||||
RouteResult(MaybeStackVector<Measure> measures, UnicodeString precision)
|
||||
: measures(std::move(measures)), precision(std::move(precision)) {}
|
||||
// The output unit for this RouteResult. This may be a MIXED unit - for
|
||||
// example: "yard-and-foot-and-inch", for which `measures` will have three
|
||||
// elements.
|
||||
MeasureUnitImpl outputUnit;
|
||||
|
||||
RouteResult(MaybeStackVector<Measure> measures, UnicodeString precision, MeasureUnitImpl outputUnit)
|
||||
: measures(std::move(measures)), precision(std::move(precision)), outputUnit(std::move(outputUnit)) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -55,6 +60,10 @@ struct ConverterPreference : UMemory {
|
|||
double limit;
|
||||
UnicodeString precision;
|
||||
|
||||
// The output unit for this ConverterPreference. This may be a MIXED unit -
|
||||
// for example: "yard-and-foot-and-inch".
|
||||
MeasureUnitImpl targetUnit;
|
||||
|
||||
// In case there is no limit, the limit will be -inf.
|
||||
ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget,
|
||||
UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status)
|
||||
|
@ -65,7 +74,7 @@ struct ConverterPreference : UMemory {
|
|||
double limit, UnicodeString precision, const ConversionRates &ratesInfo,
|
||||
UErrorCode &status)
|
||||
: converter(source, complexTarget, ratesInfo, status), limit(limit),
|
||||
precision(std::move(precision)) {}
|
||||
precision(std::move(precision)), targetUnit(complexTarget.copy(status)) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -998,7 +998,7 @@ group: numberformatter
|
|||
numrange_fluent.o numrange_impl.o
|
||||
deps
|
||||
decnumber double_conversion formattable units unitsformatter
|
||||
number_representation number_output
|
||||
listformatter number_representation number_output
|
||||
numsys
|
||||
number_usageprefs
|
||||
uclean_i18n common
|
||||
|
|
|
@ -81,6 +81,7 @@ private:
|
|||
void TestNumericTimeSomeSpecialFormats();
|
||||
void TestIdentifiers();
|
||||
void TestInvalidIdentifiers();
|
||||
void TestParseToBuiltIn();
|
||||
void TestCompoundUnitOperations();
|
||||
void TestDimensionlessBehaviour();
|
||||
void Test21060_AddressSanitizerProblem();
|
||||
|
@ -205,6 +206,7 @@ void MeasureFormatTest::runIndexedTest(
|
|||
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
|
||||
TESTCASE_AUTO(TestIdentifiers);
|
||||
TESTCASE_AUTO(TestInvalidIdentifiers);
|
||||
TESTCASE_AUTO(TestParseToBuiltIn);
|
||||
TESTCASE_AUTO(TestCompoundUnitOperations);
|
||||
TESTCASE_AUTO(TestDimensionlessBehaviour);
|
||||
TESTCASE_AUTO(Test21060_AddressSanitizerProblem);
|
||||
|
@ -3130,11 +3132,23 @@ void MeasureFormatTest::TestIndividualPluralFallback() {
|
|||
// and falls back to fr for the "other" form.
|
||||
IcuTestErrorCode errorCode(*this, "TestIndividualPluralFallback");
|
||||
MeasureFormat mf("fr_CA", UMEASFMT_WIDTH_SHORT, errorCode);
|
||||
if (errorCode.errIfFailureAndReset("MeasureFormat mf(...) failed.")) {
|
||||
return;
|
||||
}
|
||||
LocalPointer<Measure> twoDeg(
|
||||
new Measure(2.0, MeasureUnit::createGenericTemperature(errorCode), errorCode), errorCode);
|
||||
if (errorCode.errIfFailureAndReset("Creating twoDeg failed.")) {
|
||||
return;
|
||||
}
|
||||
UnicodeString expected = UNICODE_STRING_SIMPLE("2\\u00B0").unescape();
|
||||
UnicodeString actual;
|
||||
assertEquals("2 deg temp in fr_CA", expected, mf.format(twoDeg.orphan(), actual, errorCode), TRUE);
|
||||
// Formattable adopts the pointer
|
||||
mf.format(Formattable(twoDeg.orphan()), actual, errorCode);
|
||||
if (errorCode.errIfFailureAndReset("mf.format(...) failed.")) {
|
||||
return;
|
||||
}
|
||||
assertEquals("2 deg temp in fr_CA", expected, actual, TRUE);
|
||||
errorCode.errIfFailureAndReset("mf.format failed");
|
||||
}
|
||||
|
||||
void MeasureFormatTest::Test20332_PersonUnits() {
|
||||
|
@ -3321,6 +3335,33 @@ void MeasureFormatTest::TestInvalidIdentifiers() {
|
|||
}
|
||||
}
|
||||
|
||||
void MeasureFormatTest::TestParseToBuiltIn() {
|
||||
IcuTestErrorCode status(*this, "TestParseToBuiltIn()");
|
||||
const struct TestCase {
|
||||
const char *identifier;
|
||||
MeasureUnit expectedBuiltIn;
|
||||
} cases[] = {
|
||||
{"meter-per-second-per-second", MeasureUnit::getMeterPerSecondSquared()},
|
||||
{"meter-per-second-second", MeasureUnit::getMeterPerSecondSquared()},
|
||||
{"centimeter-centimeter", MeasureUnit::getSquareCentimeter()},
|
||||
{"square-foot", MeasureUnit::getSquareFoot()},
|
||||
{"pow2-inch", MeasureUnit::getSquareInch()},
|
||||
{"milligram-per-deciliter", MeasureUnit::getMilligramPerDeciliter()},
|
||||
{"pound-force-per-pow2-inch", MeasureUnit::getPoundPerSquareInch()},
|
||||
{"yard-pow2-yard", MeasureUnit::getCubicYard()},
|
||||
{"square-yard-yard", MeasureUnit::getCubicYard()},
|
||||
};
|
||||
|
||||
for (auto &cas : cases) {
|
||||
MeasureUnit fromIdent = MeasureUnit::forIdentifier(cas.identifier, status);
|
||||
status.assertSuccess();
|
||||
assertEquals("forIdentifier returns a normal built-in unit when it exists",
|
||||
cas.expectedBuiltIn.getOffset(), fromIdent.getOffset());
|
||||
assertEquals("type", cas.expectedBuiltIn.getType(), fromIdent.getType());
|
||||
assertEquals("subType", cas.expectedBuiltIn.getSubtype(), fromIdent.getSubtype());
|
||||
}
|
||||
}
|
||||
|
||||
void MeasureFormatTest::TestCompoundUnitOperations() {
|
||||
IcuTestErrorCode status(*this, "TestCompoundUnitOperations");
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
void notationScientific();
|
||||
void notationCompact();
|
||||
void unitMeasure();
|
||||
void unitPipeline();
|
||||
void unitCompoundMeasure();
|
||||
void unitUsage();
|
||||
void unitUsageErrorCodes();
|
||||
|
@ -87,6 +88,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
void localPointerCAPI();
|
||||
void toObject();
|
||||
void toDecimalNumber();
|
||||
void microPropsInternals();
|
||||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@
|
|||
#include <memory>
|
||||
#include "unicode/unum.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "number_asformat.h"
|
||||
#include "number_types.h"
|
||||
#include "number_utils.h"
|
||||
#include "numbertest.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "number_utypes.h"
|
||||
#include "number_microprops.h"
|
||||
#include "numbertest.h"
|
||||
|
||||
using number::impl::UFormattedNumberData;
|
||||
|
||||
|
@ -74,6 +75,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(notationScientific);
|
||||
TESTCASE_AUTO(notationCompact);
|
||||
TESTCASE_AUTO(unitMeasure);
|
||||
TESTCASE_AUTO(unitPipeline);
|
||||
TESTCASE_AUTO(unitCompoundMeasure);
|
||||
TESTCASE_AUTO(unitUsage);
|
||||
TESTCASE_AUTO(unitUsageErrorCodes);
|
||||
|
@ -115,6 +117,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(localPointerCAPI);
|
||||
TESTCASE_AUTO(toObject);
|
||||
TESTCASE_AUTO(toDecimalNumber);
|
||||
TESTCASE_AUTO(microPropsInternals);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
|
@ -679,24 +682,27 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
u"5 a\u00F1os");
|
||||
}
|
||||
|
||||
// TODO(hugovdm): once one of #52 and #61 has been merged into the other, move
|
||||
// down for consistent method order.
|
||||
void NumberFormatterApiTest::unitUsage() {
|
||||
UnlocalizedNumberFormatter unloc_formatter =
|
||||
NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
|
||||
|
||||
IcuTestErrorCode status(*this, "unitUsage()");
|
||||
|
||||
UnlocalizedNumberFormatter unloc_formatter;
|
||||
LocalizedNumberFormatter formatter;
|
||||
FormattedNumber formattedNum;
|
||||
UnicodeString uTestCase;
|
||||
|
||||
unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
|
||||
|
||||
uTestCase = u"unitUsage() en-ZA road";
|
||||
formatter = unloc_formatter.locale("en-ZA");
|
||||
formattedNum = formatter.formatDouble(321, status);
|
||||
status.errIfFailureAndReset("unitUsage() en-ZA road, formatDouble(...)");
|
||||
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));
|
||||
status.errIfFailureAndReset("unitUsage() en-ZA road formatDouble");
|
||||
assertTrue(
|
||||
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
|
||||
MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
|
||||
assertEquals(uTestCase, "300 m", formattedNum.toString(status));
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-ZA road",
|
||||
uTestCase.getTerminatedBuffer(),
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
|
@ -711,6 +717,7 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
u"10 m",
|
||||
u"0 m");
|
||||
|
||||
uTestCase = u"unitUsage() en-GB road";
|
||||
formatter = unloc_formatter.locale("en-GB");
|
||||
formattedNum = formatter.formatDouble(321, status);
|
||||
status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
|
||||
|
@ -724,7 +731,7 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
|
||||
U_ASSERT(status == U_ZERO_ERROR);
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-GB road",
|
||||
uTestCase.getTerminatedBuffer(),
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
|
@ -739,6 +746,7 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
u"9.6 yd",
|
||||
u"0 yd");
|
||||
|
||||
uTestCase = u"unitUsage() en-US road";
|
||||
formatter = unloc_formatter.locale("en-US");
|
||||
formattedNum = formatter.formatDouble(321, status);
|
||||
status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
|
||||
|
@ -752,7 +760,7 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
|
||||
U_ASSERT(status == U_ZERO_ERROR);
|
||||
assertFormatDescendingBig(
|
||||
u"unitUsage() en-US road",
|
||||
uTestCase.getTerminatedBuffer(),
|
||||
u"measure-unit/length-meter usage/road",
|
||||
u"unit/meter usage/road",
|
||||
unloc_formatter,
|
||||
|
@ -767,6 +775,39 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
u"30 ft",
|
||||
u"0 ft");
|
||||
|
||||
unloc_formatter = NumberFormatter::with().usage("person").unit(MeasureUnit::getKilogram());
|
||||
uTestCase = u"unitUsage() en-GB person";
|
||||
formatter = unloc_formatter.locale("en-GB");
|
||||
formattedNum = formatter.formatDouble(80, status);
|
||||
status.errIfFailureAndReset("unitUsage() en-GB person formatDouble");
|
||||
assertTrue(
|
||||
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
|
||||
MeasureUnit::forIdentifier("stone-and-pound", status) == formattedNum.getOutputUnit(status));
|
||||
status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)");
|
||||
assertEquals(uTestCase, "12 st and 8.4 lb", formattedNum.toString(status));
|
||||
assertFormatDescending(
|
||||
uTestCase.getTerminatedBuffer(),
|
||||
u"measure-unit/mass-kilogram usage/person",
|
||||
u"unit/kilogram usage/person",
|
||||
unloc_formatter,
|
||||
Locale("en-GB"),
|
||||
u"13,802 st and 7.2 lb",
|
||||
u"1,380 st and 3.5 lb",
|
||||
u"138 st and 0.35 lb",
|
||||
u"13 st and 11 lb",
|
||||
u"1 st and 5.3 lb",
|
||||
u"1 lb and 15 oz",
|
||||
u"0 lb and 3.1 oz",
|
||||
u"0 lb and 0.31 oz",
|
||||
u"0 lb and 0 oz");
|
||||
|
||||
// TODO(icu-units#60): determine appropriate ListFormatter style output. Consider:
|
||||
// * Unit Widths: narrow, short, full-name, iso-code, formal, variant,
|
||||
// hidden.
|
||||
// * List Format widths: wide, short, narrow? (From ULISTFMT_WIDTH_*.) Or is
|
||||
// it "standard", "duration", or "duration-short"? (From an internal
|
||||
// ListFormatter::createInstance method.)
|
||||
|
||||
assertFormatDescendingBig(
|
||||
u"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
|
||||
|
@ -926,6 +967,73 @@ void NumberFormatterApiTest::unitUsageSkeletons() {
|
|||
status.assertSuccess();
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitPipeline() {
|
||||
IcuTestErrorCode status(*this, "unitPipeline()");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Built-in unit, meter-per-second",
|
||||
u"measure-unit/speed-meter-per-second",
|
||||
u"~unit/meter-per-second", // TODO(icu-units#35): does not normalize as expected
|
||||
NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
u"2.4 m/s");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Built-in unit meter-per-second specified as .unit(built-in).perUnit(built-in)",
|
||||
u"measure-unit/length-meter per-measure-unit/duration-second",
|
||||
u"unit/meter-per-second", // TODO(icu-units#35): check whether desired behaviour?
|
||||
NumberFormatter::with().unit(METER).perUnit(SECOND),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s");
|
||||
|
||||
// TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIREABLE BEHAVIOUR!
|
||||
// When specifying built-in types, one can give both a unit and a perUnit.
|
||||
// Resolving to a built-in unit does not always work.
|
||||
//
|
||||
// (Unit-testing philosophy: leave enabled to demonstrate current behaviour
|
||||
// and changing behaviour in the future? Comment out to not assert this is
|
||||
// "correct"?)
|
||||
assertFormatSingle(
|
||||
u"DEMONSTRATING BAD BEHAVIOUR, TODO(icu-units#59)",
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()).perUnit(MeasureUnit::getSecond()),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s/s");
|
||||
|
||||
LocalizedNumberFormatter nf;
|
||||
FormattedNumber num;
|
||||
|
||||
// If unit is not a built-in type, perUnit is not allowed
|
||||
nf = NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("furlong-pascal", status))
|
||||
.perUnit(METER)
|
||||
.locale("en-GB");
|
||||
status.assertSuccess(); // Error is only returned once we try to format.
|
||||
num = nf.formatDouble(2.4, status);
|
||||
if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
|
||||
errln(UnicodeString("Expected failure, got: \"") +
|
||||
nf.formatDouble(2.4, status).toString(status) + "\".");
|
||||
status.assertSuccess();
|
||||
}
|
||||
|
||||
// perUnit is only allowed to be a built-in type
|
||||
nf = NumberFormatter::with()
|
||||
.unit(MeasureUnit::getMeter())
|
||||
.perUnit(MeasureUnit::forIdentifier("square-second", status))
|
||||
.locale("en-GB");
|
||||
status.assertSuccess(); // Error is only returned once we try to format.
|
||||
num = nf.formatDouble(2.4, status);
|
||||
if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
|
||||
errln(UnicodeString("Expected failure, got: \"") +
|
||||
nf.formatDouble(2.4, status).toString(status) + "\".");
|
||||
status.assertSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Per Second Short (unit that simplifies) and perUnit method",
|
||||
|
@ -3698,6 +3806,33 @@ void NumberFormatterApiTest::toDecimalNumber() {
|
|||
"9.8765E+14", fn.toDecimalNumber<std::string>(status).c_str());
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::microPropsInternals(void) {
|
||||
// Verify copy construction and assignment operators.
|
||||
int64_t testValues[2] = {4, 61};
|
||||
|
||||
MicroProps mp;
|
||||
assertEquals("capacity", 2, mp.mixedMeasures.getCapacity());
|
||||
mp.mixedMeasures[0] = testValues[0];
|
||||
mp.mixedMeasures[1] = testValues[1];
|
||||
MicroProps copyConstructed(mp);
|
||||
MicroProps copyAssigned;
|
||||
int64_t *resizeResult = mp.mixedMeasures.resize(4, 4);
|
||||
assertTrue("Resize success", resizeResult != NULL);
|
||||
copyAssigned = mp;
|
||||
|
||||
assertTrue("MicroProps success status", U_SUCCESS(mp.mixedMeasures.status));
|
||||
assertTrue("Copy Constructed success status", U_SUCCESS(copyConstructed.mixedMeasures.status));
|
||||
assertTrue("Copy Assigned success status", U_SUCCESS(copyAssigned.mixedMeasures.status));
|
||||
assertEquals("Original values[0]", testValues[0], mp.mixedMeasures[0]);
|
||||
assertEquals("Original values[1]", testValues[1], mp.mixedMeasures[1]);
|
||||
assertEquals("Copy Constructed[0]", testValues[0], copyConstructed.mixedMeasures[0]);
|
||||
assertEquals("Copy Constructed[1]", testValues[1], copyConstructed.mixedMeasures[1]);
|
||||
assertEquals("Copy Assigned[0]", testValues[0], copyAssigned.mixedMeasures[0]);
|
||||
assertEquals("Copy Assigned[1]", testValues[1], copyAssigned.mixedMeasures[1]);
|
||||
assertEquals("Original capacity", 4, mp.mixedMeasures.getCapacity());
|
||||
assertEquals("Copy Constructed capacity", 2, copyConstructed.mixedMeasures.getCapacity());
|
||||
assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity());
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::assertFormatDescending(
|
||||
const char16_t* umessage,
|
||||
|
|
Loading…
Add table
Reference in a new issue