#include "unicode/putil.h"
#include "unicode/smpdtfmt.h"
#include "uassert.h"
+#include "unicode/numberformatter.h"
+#include "number_longnames.h"
#include "sharednumberformat.h"
#include "sharedpluralrules.h"
U_NAMESPACE_BEGIN
-static constexpr int32_t PER_UNIT_INDEX = StandardPlural::COUNT;
-static constexpr int32_t PATTERN_COUNT = PER_UNIT_INDEX + 1;
static constexpr int32_t MEAS_UNIT_COUNT = 146; // see assertion in MeasureFormatCacheData constructor
static constexpr int32_t WIDTH_INDEX_COUNT = UMEASFMT_WIDTH_NARROW + 1;
return width;
}
+static UNumberUnitWidth getUnitWidth(UMeasureFormatWidth width) {
+ switch (width) {
+ case UMEASFMT_WIDTH_WIDE:
+ return UNUM_UNIT_WIDTH_FULL_NAME;
+ case UMEASFMT_WIDTH_NARROW:
+ case UMEASFMT_WIDTH_NUMERIC:
+ return UNUM_UNIT_WIDTH_NARROW;
+ case UMEASFMT_WIDTH_SHORT:
+ default:
+ return UNUM_UNIT_WIDTH_SHORT;
+ }
+}
+
/**
* Instances contain all MeasureFormat specific data for a particular locale.
* This data is cached. It is never copied, but is shared via shared pointers.
* - UMEASFMT_WIDTH_WIDE/SHORT/NARROW: sideways alias for missing data
*/
UMeasureFormatWidth widthFallback[WIDTH_INDEX_COUNT];
- /** Measure unit -> format width -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX) */
- SimpleFormatter* patterns[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT][PATTERN_COUNT];
- const UChar* dnams[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT];
- SimpleFormatter perFormatters[WIDTH_INDEX_COUNT];
MeasureFormatCacheData();
virtual ~MeasureFormatCacheData();
- UBool hasPerFormatter(int32_t width) const {
- // TODO: Create a more obvious way to test if the per-formatter has been set?
- // Use pointers, check for NULL? Or add an isValid() method?
- return perFormatters[width].getArgumentLimit() == 2;
- }
-
void adoptCurrencyFormat(int32_t widthIndex, NumberFormat *nfToAdopt) {
delete currencyFormats[widthIndex];
currencyFormats[widthIndex] = nfToAdopt;
for (int32_t i = 0; i < WIDTH_INDEX_COUNT; ++i) {
widthFallback[i] = UMEASFMT_WIDTH_COUNT;
}
- memset(&patterns[0][0][0], 0, sizeof(patterns));
- memset(&dnams[0][0], 0, sizeof(dnams));
memset(currencyFormats, 0, sizeof(currencyFormats));
}
for (int32_t i = 0; i < UPRV_LENGTHOF(currencyFormats); ++i) {
delete currencyFormats[i];
}
- for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) {
- for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) {
- for (int32_t k = 0; k < PATTERN_COUNT; ++k) {
- delete patterns[i][j][k];
- }
- }
- }
// Note: the contents of 'dnams' are pointers into the resource bundle
delete integerFormat;
delete numericDateFormatters;
return TRUE;
}
-namespace {
-
-static const UChar g_LOCALE_units[] = {
- 0x2F, 0x4C, 0x4F, 0x43, 0x41, 0x4C, 0x45, 0x2F,
- 0x75, 0x6E, 0x69, 0x74, 0x73
-};
-static const UChar gShort[] = { 0x53, 0x68, 0x6F, 0x72, 0x74 };
-static const UChar gNarrow[] = { 0x4E, 0x61, 0x72, 0x72, 0x6F, 0x77 };
-
-/**
- * Sink for enumerating all of the measurement unit display names.
- * Contains inner sink classes, each one corresponding to a type of resource table.
- * The outer sink handles the top-level units, unitsNarrow, and unitsShort tables.
- *
- * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
- * Only store a value if it is still missing, that is, it has not been overridden.
- *
- * C++: Each inner sink class has a reference to the main outer sink.
- * Java: Use non-static inner classes instead.
- */
-struct UnitDataSink : public ResourceSink {
-
- // Output data.
- MeasureFormatCacheData &cacheData;
-
- // Path to current data.
- UMeasureFormatWidth width;
- const char *type;
- int32_t unitIndex;
-
- UnitDataSink(MeasureFormatCacheData &outputData)
- : cacheData(outputData),
- width(UMEASFMT_WIDTH_COUNT), type(NULL), unitIndex(0) {}
- ~UnitDataSink();
-
- void setFormatterIfAbsent(int32_t index, const ResourceValue &value,
- int32_t minPlaceholders, UErrorCode &errorCode) {
- U_ASSERT(unitIndex < MEAS_UNIT_COUNT);
- U_ASSERT(width < WIDTH_INDEX_COUNT);
- U_ASSERT(index < PATTERN_COUNT);
- SimpleFormatter **patterns = &cacheData.patterns[unitIndex][width][0];
- if (U_SUCCESS(errorCode) && patterns[index] == NULL) {
- if (minPlaceholders >= 0) {
- patterns[index] = new SimpleFormatter(
- value.getUnicodeString(errorCode), minPlaceholders, 1, errorCode);
- }
- if (U_SUCCESS(errorCode) && patterns[index] == NULL) {
- errorCode = U_MEMORY_ALLOCATION_ERROR;
- }
- }
- }
-
- void setDnamIfAbsent(const ResourceValue &value, UErrorCode& errorCode) {
- U_ASSERT(unitIndex < MEAS_UNIT_COUNT);
- U_ASSERT(width < WIDTH_INDEX_COUNT);
- if (cacheData.dnams[unitIndex][width] == NULL) {
- int32_t length;
- cacheData.dnams[unitIndex][width] = value.getString(length, errorCode);
- }
- }
-
- /**
- * Consume a display pattern. For example,
- * unitsShort/duration/hour contains other{"{0} hrs"}.
- */
- void consumePattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
- if (U_FAILURE(errorCode)) { return; }
- if (uprv_strcmp(key, "dnam") == 0) {
- // The display name for the unit in the current width.
- setDnamIfAbsent(value, errorCode);
- } else if (uprv_strcmp(key, "per") == 0) {
- // For example, "{0}/h".
- setFormatterIfAbsent(PER_UNIT_INDEX, value, 1, errorCode);
- } else {
- // The key must be one of the plural form strings. For example:
- // one{"{0} hr"}
- // other{"{0} hrs"}
- setFormatterIfAbsent(StandardPlural::indexFromString(key, errorCode), value, 0,
- errorCode);
- }
- }
-
- /**
- * Consume a table of per-unit tables. For example,
- * unitsShort/duration contains tables for duration-unit subtypes day & hour.
- */
- void consumeSubtypeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) {
- if (U_FAILURE(errorCode)) { return; }
- unitIndex = MeasureUnit::internalGetIndexForTypeAndSubtype(type, key);
- if (unitIndex < 0) {
- // TODO: How to handle unexpected data?
- // See http://bugs.icu-project.org/trac/ticket/12597
- return;
- }
-
- // We no longer handle units like "coordinate" here (which do not have plural variants)
- if (value.getType() == URES_TABLE) {
- // Units that have plural variants
- ResourceTable patternTableTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i = 0; patternTableTable.getKeyAndValue(i, key, value); ++i) {
- consumePattern(key, value, errorCode);
- }
- } else {
- // TODO: How to handle unexpected data?
- // See http://bugs.icu-project.org/trac/ticket/12597
- return;
- }
- }
-
- /**
- * Consume compound x-per-y display pattern. For example,
- * unitsShort/compound/per may be "{0}/{1}".
- */
- void consumeCompoundPattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
- if (U_SUCCESS(errorCode) && uprv_strcmp(key, "per") == 0) {
- cacheData.perFormatters[width].
- applyPatternMinMaxArguments(value.getUnicodeString(errorCode), 2, 2, errorCode);
- }
- }
-
- /**
- * Consume a table of unit type tables. For example,
- * unitsShort contains tables for area & duration.
- * It also contains a table for the compound/per pattern.
- */
- void consumeUnitTypesTable(const char *key, ResourceValue &value, UErrorCode &errorCode) {
- if (U_FAILURE(errorCode)) { return; }
- if (uprv_strcmp(key, "currency") == 0) {
- // Skip.
- } else if (uprv_strcmp(key, "compound") == 0) {
- if (!cacheData.hasPerFormatter(width)) {
- ResourceTable compoundTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i = 0; compoundTable.getKeyAndValue(i, key, value); ++i) {
- consumeCompoundPattern(key, value, errorCode);
- }
- }
- } else if (uprv_strcmp(key, "coordinate") == 0) {
- // special handling but we need to determine what that is
- } else {
- type = key;
- ResourceTable subtypeTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i = 0; subtypeTable.getKeyAndValue(i, key, value); ++i) {
- consumeSubtypeTable(key, value, errorCode);
- }
- }
- }
-
- void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
- // Handle aliases like
- // units:alias{"/LOCALE/unitsShort"}
- // which should only occur in the root bundle.
- UMeasureFormatWidth sourceWidth = widthFromKey(key);
- if (sourceWidth == UMEASFMT_WIDTH_COUNT) {
- // Alias from something we don't care about.
- return;
- }
- UMeasureFormatWidth targetWidth = widthFromAlias(value, errorCode);
- if (targetWidth == UMEASFMT_WIDTH_COUNT) {
- // We do not recognize what to fall back to.
- errorCode = U_INVALID_FORMAT_ERROR;
- return;
- }
- // Check that we do not fall back to another fallback.
- if (cacheData.widthFallback[targetWidth] != UMEASFMT_WIDTH_COUNT) {
- errorCode = U_INVALID_FORMAT_ERROR;
- return;
- }
- cacheData.widthFallback[sourceWidth] = targetWidth;
- }
-
- void consumeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) {
- if (U_SUCCESS(errorCode) && (width = widthFromKey(key)) != UMEASFMT_WIDTH_COUNT) {
- ResourceTable unitTypesTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
- consumeUnitTypesTable(key, value, errorCode);
- }
- }
- }
-
- static UMeasureFormatWidth widthFromKey(const char *key) {
- if (uprv_strncmp(key, "units", 5) == 0) {
- key += 5;
- if (*key == 0) {
- return UMEASFMT_WIDTH_WIDE;
- } else if (uprv_strcmp(key, "Short") == 0) {
- return UMEASFMT_WIDTH_SHORT;
- } else if (uprv_strcmp(key, "Narrow") == 0) {
- return UMEASFMT_WIDTH_NARROW;
- }
- }
- return UMEASFMT_WIDTH_COUNT;
- }
-
- static UMeasureFormatWidth widthFromAlias(const ResourceValue &value, UErrorCode &errorCode) {
- int32_t length;
- const UChar *s = value.getAliasString(length, errorCode);
- // For example: "/LOCALE/unitsShort"
- if (U_SUCCESS(errorCode) && length >= 13 && u_memcmp(s, g_LOCALE_units, 13) == 0) {
- s += 13;
- length -= 13;
- if (*s == 0) {
- return UMEASFMT_WIDTH_WIDE;
- } else if (u_strCompare(s, length, gShort, 5, FALSE) == 0) {
- return UMEASFMT_WIDTH_SHORT;
- } else if (u_strCompare(s, length, gNarrow, 6, FALSE) == 0) {
- return UMEASFMT_WIDTH_NARROW;
- }
- }
- return UMEASFMT_WIDTH_COUNT;
- }
-
- virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/,
- UErrorCode &errorCode) {
- // Main entry point to sink
- ResourceTable widthsTable = value.getTable(errorCode);
- if (U_FAILURE(errorCode)) { return; }
- for (int i = 0; widthsTable.getKeyAndValue(i, key, value); ++i) {
- if (value.getType() == URES_ALIAS) {
- consumeAlias(key, value, errorCode);
- } else {
- consumeTable(key, value, errorCode);
- }
- }
- }
-};
-
-// Virtual destructors must be defined out of line.
-UnitDataSink::~UnitDataSink() {}
-
-} // namespace
-
-static UBool loadMeasureUnitData(
- const UResourceBundle *resource,
- MeasureFormatCacheData &cacheData,
- UErrorCode &status) {
- UnitDataSink sink(cacheData);
- ures_getAllItemsWithFallback(resource, "", sink, status);
- return U_SUCCESS(status);
-}
-
static UnicodeString loadNumericDateFormatterPattern(
const UResourceBundle *resource,
const char *pattern,
if (U_FAILURE(status)) {
return NULL;
}
- if (!loadMeasureUnitData(
- unitsBundle.getAlias(),
- *result,
- status)) {
- return NULL;
- }
result->adoptNumericDateFormatters(loadNumericDateFormatters(
unitsBundle.getAlias(), status));
if (U_FAILURE(status)) {
if (U_FAILURE(status)) {
return appendTo;
}
- bool isResolved = false;
- MeasureUnit resolvedUnit =
- MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit, &isResolved);
- if (isResolved) {
- Measure newMeasure(measure.getNumber(), new MeasureUnit(resolvedUnit), status);
- return formatMeasure(
- newMeasure, **numberFormat, appendTo, pos, status);
- }
- FieldPosition fpos(pos.getField());
- UnicodeString result;
- int32_t offset = withPerUnitAndAppend(
- formatMeasure(
- measure, **numberFormat, result, fpos, status),
- perUnit,
- appendTo,
- status);
- if (U_FAILURE(status)) {
+ auto* df = dynamic_cast<const DecimalFormat*>(&getNumberFormatInternal());
+ if (df == nullptr) {
+ // Don't know how to handle other types of NumberFormat
+ status = U_UNSUPPORTED_ERROR;
return appendTo;
}
- if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
- pos.setBeginIndex(fpos.getBeginIndex() + offset);
- pos.setEndIndex(fpos.getEndIndex() + offset);
+ number::FormattedNumber result;
+ if (auto* lnf = df->toNumberFormatter(status)) {
+ result = lnf->unit(measure.getUnit())
+ .perUnit(perUnit)
+ .unitWidth(getUnitWidth(fWidth))
+ .formatDouble(measure.getNumber().getDouble(status), status);
}
+ DecimalFormat::fieldPositionHelper(result, pos, appendTo.length(), status);
+ appendTo.append(result.toTempString(status));
return appendTo;
}
return appendTo;
}
-UnicodeString MeasureFormat::getUnitDisplayName(const MeasureUnit& unit, UErrorCode& /*status*/) const {
- UMeasureFormatWidth width = getRegularWidth(fWidth);
- const UChar* const* styleToDnam = cache->dnams[unit.getIndex()];
- const UChar* dnam = styleToDnam[width];
- if (dnam == NULL) {
- int32_t fallbackWidth = cache->widthFallback[width];
- dnam = styleToDnam[fallbackWidth];
- }
-
- UnicodeString result;
- if (dnam == NULL) {
- result.setToBogus();
- } else {
- result.setTo(dnam, -1);
- }
- return result;
+UnicodeString MeasureFormat::getUnitDisplayName(const MeasureUnit& unit, UErrorCode& status) const {
+ return number::impl::LongNameHandler::getUnitDisplayName(
+ getLocale(status),
+ unit,
+ getUnitWidth(fWidth),
+ status);
}
void MeasureFormat::initMeasureFormat(
SharedObject::copyPtr(pr, pluralRules);
pr->removeRef();
if (nf.isNull()) {
+ // TODO: Clean this up
const SharedNumberFormat *shared = NumberFormat::createSharedInstance(
locale, UNUM_DECIMAL, status);
if (U_FAILURE(status)) {
return U_SUCCESS(status);
}
-const NumberFormat &MeasureFormat::getNumberFormat() const {
+const NumberFormat &MeasureFormat::getNumberFormatInternal() const {
return **numberFormat;
}
+const NumberFormat &MeasureFormat::getCurrencyFormatInternal() const {
+ return *cache->getCurrencyFormat(UMEASFMT_WIDTH_NARROW);
+}
+
const PluralRules &MeasureFormat::getPluralRules() const {
return **pluralRules;
}
pos,
status);
}
- UnicodeString formattedNumber;
- StandardPlural::Form pluralForm = QuantityFormatter::selectPlural(
- amtNumber, nf, **pluralRules, formattedNumber, pos, status);
- const SimpleFormatter *formatter = getPluralFormatter(amtUnit, fWidth, pluralForm, status);
- return QuantityFormatter::format(*formatter, formattedNumber, appendTo, pos, status);
+ auto* df = dynamic_cast<const DecimalFormat*>(&nf);
+ if (df == nullptr) {
+ // Don't know how to handle other types of NumberFormat
+ status = U_UNSUPPORTED_ERROR;
+ return appendTo;
+ }
+ number::FormattedNumber result;
+ if (auto* lnf = df->toNumberFormatter(status)) {
+ result = lnf->unit(amtUnit)
+ .unitWidth(getUnitWidth(fWidth))
+ .formatDouble(amtNumber.getDouble(status), status);
+ }
+ DecimalFormat::fieldPositionHelper(result, pos, appendTo.length(), status);
+ appendTo.append(result.toTempString(status));
+ return appendTo;
}
// Formats hours-minutes-seconds as 5:37:23 or similar.
return appendTo;
}
-const SimpleFormatter *MeasureFormat::getFormatterOrNull(
- const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const {
- width = getRegularWidth(width);
- SimpleFormatter *const (*unitPatterns)[PATTERN_COUNT] = &cache->patterns[unit.getIndex()][0];
- if (unitPatterns[width][index] != NULL) {
- return unitPatterns[width][index];
- }
- int32_t fallbackWidth = cache->widthFallback[width];
- if (fallbackWidth != UMEASFMT_WIDTH_COUNT && unitPatterns[fallbackWidth][index] != NULL) {
- return unitPatterns[fallbackWidth][index];
- }
- return NULL;
-}
-
-const SimpleFormatter *MeasureFormat::getFormatter(
- const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index,
- UErrorCode &errorCode) const {
- if (U_FAILURE(errorCode)) {
- return NULL;
- }
- const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index);
- if (pattern == NULL) {
- errorCode = U_MISSING_RESOURCE_ERROR;
- }
- return pattern;
-}
-
-const SimpleFormatter *MeasureFormat::getPluralFormatter(
- const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index,
- UErrorCode &errorCode) const {
- if (U_FAILURE(errorCode)) {
- return NULL;
- }
- if (index != StandardPlural::OTHER) {
- const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index);
- if (pattern != NULL) {
- return pattern;
- }
- }
- return getFormatter(unit, width, StandardPlural::OTHER, errorCode);
-}
-
-const SimpleFormatter *MeasureFormat::getPerFormatter(
- UMeasureFormatWidth width,
- UErrorCode &status) const {
- if (U_FAILURE(status)) {
- return NULL;
- }
- width = getRegularWidth(width);
- const SimpleFormatter * perFormatters = cache->perFormatters;
- if (perFormatters[width].getArgumentLimit() == 2) {
- return &perFormatters[width];
- }
- int32_t fallbackWidth = cache->widthFallback[width];
- if (fallbackWidth != UMEASFMT_WIDTH_COUNT &&
- perFormatters[fallbackWidth].getArgumentLimit() == 2) {
- return &perFormatters[fallbackWidth];
- }
- status = U_MISSING_RESOURCE_ERROR;
- return NULL;
-}
-
-int32_t MeasureFormat::withPerUnitAndAppend(
- const UnicodeString &formatted,
- const MeasureUnit &perUnit,
- UnicodeString &appendTo,
- UErrorCode &status) const {
- int32_t offset = -1;
- if (U_FAILURE(status)) {
- return offset;
- }
- const SimpleFormatter *perUnitFormatter = getFormatterOrNull(perUnit, fWidth, PER_UNIT_INDEX);
- if (perUnitFormatter != NULL) {
- const UnicodeString *params[] = {&formatted};
- perUnitFormatter->formatAndAppend(
- params,
- UPRV_LENGTHOF(params),
- appendTo,
- &offset,
- 1,
- status);
- return offset;
- }
- const SimpleFormatter *perFormatter = getPerFormatter(fWidth, status);
- const SimpleFormatter *pattern =
- getPluralFormatter(perUnit, fWidth, StandardPlural::ONE, status);
- if (U_FAILURE(status)) {
- return offset;
- }
- UnicodeString perUnitString = pattern->getTextWithNoArguments();
- perUnitString.trim();
- const UnicodeString *params[] = {&formatted, &perUnitString};
- perFormatter->formatAndAppend(
- params,
- UPRV_LENGTHOF(params),
- appendTo,
- &offset,
- 1,
- status);
- return offset;
-}
-
UnicodeString &MeasureFormat::formatMeasuresSlowTrack(
const Measure *measures,
int32_t measureCount,
}
FieldPosition dontCare(FieldPosition::DONT_CARE);
FieldPosition fpos(pos.getField());
- UnicodeString *results = new UnicodeString[measureCount];
+ LocalArray<UnicodeString> results(new UnicodeString[measureCount], status);
int32_t fieldPositionFoundIndex = -1;
for (int32_t i = 0; i < measureCount; ++i) {
const NumberFormat *nf = cache->getIntegerFormat();
if (fieldPositionFoundIndex == -1) {
formatMeasure(measures[i], *nf, results[i], fpos, status);
if (U_FAILURE(status)) {
- delete [] results;
return appendTo;
}
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
}
int32_t offset;
listFormatter->format(
- results,
+ results.getAlias(),
measureCount,
appendTo,
fieldPositionFoundIndex,
offset,
status);
if (U_FAILURE(status)) {
- delete [] results;
return appendTo;
}
+ // Fix up FieldPosition indexes if our field is found.
if (offset != -1) {
pos.setBeginIndex(fpos.getBeginIndex() + offset);
pos.setEndIndex(fpos.getEndIndex() + offset);
}
- delete [] results;
return appendTo;
}
MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(const Locale& locale,
UErrorCode& ec) {
- CurrencyFormat* fmt = NULL;
- if (U_SUCCESS(ec)) {
- fmt = new CurrencyFormat(locale, ec);
- if (U_FAILURE(ec)) {
- delete fmt;
- fmt = NULL;
- }
+ if (U_FAILURE(ec)) {
+ return nullptr;
}
- return fmt;
+ LocalPointer<CurrencyFormat> fmt(new CurrencyFormat(locale, ec), ec);
+ return fmt.orphan();
}
MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(UErrorCode& ec) {
if (U_FAILURE(ec)) {
- return NULL;
+ return nullptr;
}
return MeasureFormat::createCurrencyFormat(Locale::getDefault(), ec);
}