*/
UBool startsWithPlaceholder(int32_t id) const;
+ /**
+ * Returns this pattern with none of the placeholders.
+ */
+ const UnicodeString &getPatternWithNoPlaceholders() const {
+ return noPlaceholders;
+ }
+
/**
* Formats given value.
*/
#include "unicode/numfmt.h"
#include "currfmt.h"
#include "unicode/localpointer.h"
+#include "simplepatternformatter.h"
#include "quantityformatter.h"
#include "unicode/plurrule.h"
#include "unicode/decimfmt.h"
class MeasureFormatCacheData : public SharedObject {
public:
QuantityFormatter formatters[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT];
+ SimplePatternFormatter perFormatters[WIDTH_INDEX_COUNT];
+
MeasureFormatCacheData();
void adoptCurrencyFormat(int32_t widthIndex, NumberFormat *nfToAdopt) {
delete currencyFormats[widthIndex];
const NumericDateFormatters *getNumericDateFormatters() const {
return numericDateFormatters;
}
+ void adoptPerUnitFormatter(
+ int32_t index,
+ int32_t widthIndex,
+ SimplePatternFormatter *formatterToAdopt) {
+ delete perUnitFormatters[index][widthIndex];
+ perUnitFormatters[index][widthIndex] = formatterToAdopt;
+ }
+ const SimplePatternFormatter * const * getPerUnitFormattersByIndex(
+ int32_t index) const {
+ return perUnitFormatters[index];
+ }
virtual ~MeasureFormatCacheData();
private:
NumberFormat *currencyFormats[WIDTH_INDEX_COUNT];
NumberFormat *integerFormat;
NumericDateFormatters *numericDateFormatters;
+ SimplePatternFormatter *perUnitFormatters[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT];
MeasureFormatCacheData(const MeasureFormatCacheData &other);
MeasureFormatCacheData &operator=(const MeasureFormatCacheData &other);
};
for (int32_t i = 0; i < UPRV_LENGTHOF(currencyFormats); ++i) {
currencyFormats[i] = NULL;
}
+ for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) {
+ for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) {
+ perUnitFormatters[i][j] = NULL;
+ }
+ }
integerFormat = NULL;
numericDateFormatters = NULL;
}
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) {
+ delete perUnitFormatters[i][j];
+ }
+ }
delete integerFormat;
delete numericDateFormatters;
}
status = U_ZERO_ERROR;
continue;
}
+ {
+ // compound per
+ LocalUResourceBundlePointer compoundPerBundle(
+ ures_getByKeyWithFallback(
+ widthBundle.getAlias(),
+ "compound/per",
+ NULL,
+ &status));
+ if (U_FAILURE(status)) {
+ status = U_ZERO_ERROR;
+ } else {
+ UnicodeString perPattern;
+ getString(compoundPerBundle.getAlias(), perPattern, status);
+ cacheData.perFormatters[currentWidth].compile(perPattern, status);
+ }
+ }
for (int32_t currentUnit = 0; currentUnit < unitCount; ++currentUnit) {
// Be sure status is clear next lookup may fail.
if (U_FAILURE(status)) {
return FALSE;
}
const char * resKey = ures_getKey(pluralBundle.getAlias());
- if (uprv_strcmp(resKey, "dnam") == 0 || uprv_strcmp(resKey, "per") == 0) {
+ if (uprv_strcmp(resKey, "dnam") == 0) {
continue; // skip display name & per pattern (new in CLDR 26 / ICU 54) for now, not part of plurals
}
+ if (uprv_strcmp(resKey, "per") == 0) {
+ UnicodeString perPattern;
+ getString(pluralBundle.getAlias(), perPattern, status);
+ cacheData.adoptPerUnitFormatter(
+ units[currentUnit].getIndex(),
+ currentWidth,
+ new SimplePatternFormatter(perPattern));
+ continue;
+ }
UnicodeString rawPattern;
getString(pluralBundle.getAlias(), rawPattern, status);
cacheData.formatters[units[currentUnit].getIndex()][currentWidth].add(
return;
}
+UnicodeString &MeasureFormat::formatMeasuresPer(
+ const Measure *measures,
+ int32_t measureCount,
+ const MeasureUnit &perUnit,
+ UnicodeString &appendTo,
+ FieldPosition &pos,
+ UErrorCode &status) const {
+ FieldPosition fpos(pos.getField());
+ UnicodeString measuresString;
+ int32_t offset = withPerUnit(
+ formatMeasures(
+ measures, measureCount, measuresString, fpos, status),
+ perUnit,
+ appendTo,
+ status);
+ if (U_FAILURE(status)) {
+ return appendTo;
+ }
+ if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
+ pos.setBeginIndex(fpos.getBeginIndex() + offset);
+ pos.setEndIndex(fpos.getEndIndex() + offset);
+ }
+ return appendTo;
+}
+
UnicodeString &MeasureFormat::formatMeasures(
const Measure *measures,
int32_t measureCount,
return NULL;
}
+const SimplePatternFormatter *MeasureFormat::getPerUnitFormatter(
+ int32_t index,
+ int32_t widthIndex) const {
+ const SimplePatternFormatter * const * perUnitFormatters =
+ cache->getPerUnitFormattersByIndex(index);
+ if (perUnitFormatters[widthIndex] != NULL) {
+ return perUnitFormatters[widthIndex];
+ }
+ if (perUnitFormatters[UMEASFMT_WIDTH_SHORT] != NULL) {
+ return perUnitFormatters[UMEASFMT_WIDTH_SHORT];
+ }
+ if (perUnitFormatters[UMEASFMT_WIDTH_WIDE] != NULL) {
+ return perUnitFormatters[UMEASFMT_WIDTH_WIDE];
+ }
+ return NULL;
+}
+
+const SimplePatternFormatter *MeasureFormat::getPerFormatter(
+ int32_t widthIndex,
+ UErrorCode &status) const {
+ if (U_FAILURE(status)) {
+ return NULL;
+ }
+ const SimplePatternFormatter * perFormatters = cache->perFormatters;
+
+ if (perFormatters[widthIndex].getPlaceholderCount() == 2) {
+ return &perFormatters[widthIndex];
+ }
+ if (perFormatters[UMEASFMT_WIDTH_SHORT].getPlaceholderCount() == 2) {
+ return &perFormatters[UMEASFMT_WIDTH_SHORT];
+ }
+ if (perFormatters[UMEASFMT_WIDTH_WIDE].getPlaceholderCount() == 2) {
+ return &perFormatters[UMEASFMT_WIDTH_WIDE];
+ }
+ status = U_MISSING_RESOURCE_ERROR;
+ return NULL;
+}
+
+static void getPerUnitString(
+ const QuantityFormatter &formatter,
+ UnicodeString &result) {
+ result = formatter.getByVariant("one")->getPatternWithNoPlaceholders();
+ result.trim();
+}
+
+int32_t MeasureFormat::withPerUnit(
+ const UnicodeString &formatted,
+ const MeasureUnit &perUnit,
+ UnicodeString &appendTo,
+ UErrorCode &status) const {
+ int32_t offset = -1;
+ if (U_FAILURE(status)) {
+ return offset;
+ }
+ const SimplePatternFormatter *perUnitFormatter = getPerUnitFormatter(
+ perUnit.getIndex(), widthToIndex(width));
+ if (perUnitFormatter != NULL) {
+ const UnicodeString *params[] = {&formatted};
+ perUnitFormatter->format(
+ params,
+ UPRV_LENGTHOF(params),
+ appendTo,
+ &offset,
+ 1,
+ status);
+ return offset;
+ }
+ const SimplePatternFormatter *perFormatter = getPerFormatter(
+ widthToIndex(width), status);
+ const QuantityFormatter *qf = getQuantityFormatter(
+ perUnit.getIndex(), widthToIndex(width), status);
+ if (U_FAILURE(status)) {
+ return offset;
+ }
+ UnicodeString perUnitString;
+ getPerUnitString(*qf, perUnitString);
+ const UnicodeString *params[] = {&formatted, &perUnitString};
+ perFormatter->format(
+ params,
+ UPRV_LENGTHOF(params),
+ appendTo,
+ &offset,
+ 1,
+ status);
+ return offset;
+}
+
UnicodeString &MeasureFormat::formatMeasuresSlowTrack(
const Measure *measures,
int32_t measureCount,
return formatters[0] != NULL;
}
+const SimplePatternFormatter *QuantityFormatter::getByVariant(
+ const char *variant) const {
+ int32_t pluralIndex = getPluralIndex(variant);
+ if (pluralIndex == -1) {
+ pluralIndex = 0;
+ }
+ const SimplePatternFormatter *pattern = formatters[pluralIndex];
+ if (pattern == NULL) {
+ pattern = formatters[0];
+ }
+ return pattern;
+}
+
UnicodeString &QuantityFormatter::format(
const Formattable& quantity,
const NumberFormat &fmt,
if (U_FAILURE(status)) {
return appendTo;
}
- int32_t pluralIndex = getPluralIndex(buffer.data());
- if (pluralIndex == -1) {
- pluralIndex = 0;
- }
- const SimplePatternFormatter *pattern = formatters[pluralIndex];
- if (pattern == NULL) {
- pattern = formatters[0];
- }
+ const SimplePatternFormatter *pattern = getByVariant(buffer.data());
if (pattern == NULL) {
status = U_INVALID_STATE_ERROR;
return appendTo;
*/
UBool isValid() const;
+ /**
+ * Gets the pattern formatter that would be used for a particular variant.
+ * If isValid() returns TRUE, this method is guaranteed to return a
+ * non-NULL value.
+ */
+ const SimplePatternFormatter *getByVariant(const char *variant) const;
+
/**
* Formats a quantity with this object appending the result to appendTo.
* At least the "other" variant must be added to this object for this
U_NAMESPACE_BEGIN
class Measure;
+class MeasureUnit;
class NumberFormat;
class PluralRules;
class MeasureFormatCacheData;
class SharedNumberFormat;
class SharedPluralRules;
class QuantityFormatter;
+class SimplePatternFormatter;
class ListFormatter;
class DateFormat;
UErrorCode &status) const;
#endif /* U_HIDE_DRAFT_API */
+#ifndef U_HIDE_INTERNAL_API
+ /**
+ * Works like formatMeasures but adds a per unit. An example of such a
+ * formatted string is 3 meters, 3.5 centimeters per second.
+ * @param measures array of measure objects.
+ * @param measureCount the number of measure objects.
+ * @param perUnit The per unit. In the example formatted string,
+ * it is *MeasureUnit::createSecond(status).
+ * @param appendTo formatted string appended here.
+ * @param pos the field position.
+ * @param status the error.
+ * @return appendTo reference
+ *
+ * @internal
+ */
+ UnicodeString &formatMeasuresPer(
+ const Measure *measures,
+ int32_t measureCount,
+ const MeasureUnit &perUnit,
+ UnicodeString &appendTo,
+ FieldPosition &pos,
+ UErrorCode &status) const;
+#endif /* U_HIDE_INTERNAL_API */
/**
* Return a formatter for CurrencyAmount objects in the given
int32_t widthIndex,
UErrorCode &status) const;
+ const SimplePatternFormatter *getPerUnitFormatter(
+ int32_t index,
+ int32_t widthIndex) const;
+
+ const SimplePatternFormatter *getPerFormatter(
+ int32_t widthIndex,
+ UErrorCode &status) const;
+
+ int32_t withPerUnit(
+ const UnicodeString &formatted,
+ const MeasureUnit &perUnit,
+ UnicodeString &appendTo,
+ UErrorCode &status) const;
+
UnicodeString &formatMeasure(
const Measure &measure,
const NumberFormat &nf,
void TestGreek();
void TestFormatSingleArg();
void TestFormatMeasuresZeroArg();
+ void TestMultiplesWithPer();
void TestMultiples();
void TestGram();
void TestCurrencies();
void TestFieldPosition();
void TestFieldPositionMultiple();
+ void TestFieldPositionMultipleWithPer();
void TestBadArg();
void TestEquality();
void TestGroupingSeparator();
const MeasureFormat &fmt,
const ExpectedResult *expectedResults,
int32_t count);
+ void helperTestMultiplesWithPer(
+ const Locale &locale,
+ UMeasureFormatWidth width,
+ const MeasureUnit &unit,
+ const char *expected);
void helperTestMultiples(
const Locale &locale,
UMeasureFormatWidth width,
NumberFormat::EAlignmentFields field,
int32_t start,
int32_t end);
+ void verifyFieldPositionWithPer(
+ const char *description,
+ const MeasureFormat &fmt,
+ const UnicodeString &prefix,
+ const Measure *measures,
+ int32_t measureCount,
+ const MeasureUnit &perUnit,
+ NumberFormat::EAlignmentFields field,
+ int32_t start,
+ int32_t end);
};
void MeasureFormatTest::runIndexedTest(
TESTCASE_AUTO(TestGreek);
TESTCASE_AUTO(TestFormatSingleArg);
TESTCASE_AUTO(TestFormatMeasuresZeroArg);
+ TESTCASE_AUTO(TestMultiplesWithPer);
TESTCASE_AUTO(TestMultiples);
TESTCASE_AUTO(TestGram);
TESTCASE_AUTO(TestCurrencies);
TESTCASE_AUTO(TestFieldPosition);
TESTCASE_AUTO(TestFieldPositionMultiple);
+ TESTCASE_AUTO(TestFieldPositionMultipleWithPer);
TESTCASE_AUTO(TestBadArg);
TESTCASE_AUTO(TestEquality);
TESTCASE_AUTO(TestGroupingSeparator);
verifyFormat("TestFormatMeasuresZeroArg", fmt, NULL, 0, "");
}
+void MeasureFormatTest::TestMultiplesWithPer() {
+ Locale en("en");
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<MeasureUnit> second(MeasureUnit::createSecond(status));
+ LocalPointer<MeasureUnit> minute(MeasureUnit::createMinute(status));
+ if (!assertSuccess("", status)) {
+ return;
+ }
+
+ // Per unit test.
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_WIDE, *second, "2 miles, 1 foot, 2.3 inches per second");
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_SHORT, *second, "2 mi, 1 ft, 2.3 inps");
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_NARROW, *second, "2mi 1\\u2032 2.3\\u2033/s");
+
+ // Fallback compound per test
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_WIDE, *minute, "2 miles, 1 foot, 2.3 inches per minute");
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_SHORT, *minute, "2 mi, 1 ft, 2.3 in/min");
+ helperTestMultiplesWithPer(
+ en, UMEASFMT_WIDTH_NARROW, *minute, "2mi 1\\u2032 2.3\\u2033/m");
+}
+
+void MeasureFormatTest::helperTestMultiplesWithPer(
+ const Locale &locale,
+ UMeasureFormatWidth width,
+ const MeasureUnit &perUnit,
+ const char *expected) {
+ UErrorCode status = U_ZERO_ERROR;
+ FieldPosition pos(0);
+ MeasureFormat fmt(locale, width, status);
+ if (!assertSuccess("Error creating format object", status)) {
+ return;
+ }
+ Measure measures[] = {
+ Measure(2, MeasureUnit::createMile(status), status),
+ Measure(1, MeasureUnit::createFoot(status), status),
+ Measure(2.3, MeasureUnit::createInch(status), status)};
+ if (!assertSuccess("Error creating measures", status)) {
+ return;
+ }
+ UnicodeString buffer;
+ fmt.formatMeasuresPer(
+ measures,
+ UPRV_LENGTHOF(measures),
+ perUnit,
+ buffer,
+ pos,
+ status);
+ if (!assertSuccess("Error formatting measures with per", status)) {
+ return;
+ }
+ assertEquals(
+ "TestMultiplesWithPer",
+ UnicodeString(expected).unescape(),
+ buffer);
+}
+
void MeasureFormatTest::TestMultiples() {
Locale ru("ru");
Locale en("en");
0);
}
+void MeasureFormatTest::TestFieldPositionMultipleWithPer() {
+ UErrorCode status = U_ZERO_ERROR;
+ MeasureFormat fmt("en", UMEASFMT_WIDTH_SHORT, status);
+ if (!assertSuccess("Error creating format object", status)) {
+ return;
+ }
+ Measure first[] = {
+ Measure(354, MeasureUnit::createMeter(status), status),
+ Measure(23, MeasureUnit::createCentimeter(status), status)};
+ Measure second[] = {
+ Measure(354, MeasureUnit::createMeter(status), status),
+ Measure(23, MeasureUnit::createCentimeter(status), status),
+ Measure(5.4, MeasureUnit::createMillimeter(status), status)};
+ Measure third[] = {
+ Measure(3, MeasureUnit::createMeter(status), status),
+ Measure(23, MeasureUnit::createCentimeter(status), status),
+ Measure(5, MeasureUnit::createMillimeter(status), status)};
+ if (!assertSuccess("Error creating measure objects", status)) {
+ return;
+ }
+ UnicodeString prefix("123456: ");
+
+ LocalPointer<MeasureUnit> secondUnit(MeasureUnit::createSecond(status));
+ LocalPointer<MeasureUnit> minuteUnit(MeasureUnit::createMinute(status));
+ if (!assertSuccess("Error creating format object", status)) {
+ return;
+ }
+
+ // per unit test
+ verifyFieldPositionWithPer(
+ "Integer",
+ fmt,
+ prefix,
+ first,
+ UPRV_LENGTHOF(first),
+ *secondUnit,
+ NumberFormat::kIntegerField,
+ 8,
+ 11);
+ verifyFieldPositionWithPer(
+ "Decimal separator",
+ fmt,
+ prefix,
+ second,
+ UPRV_LENGTHOF(second),
+ *secondUnit,
+ NumberFormat::kDecimalSeparatorField,
+ 23,
+ 24);
+ verifyFieldPositionWithPer(
+ "no decimal separator",
+ fmt,
+ prefix,
+ third,
+ UPRV_LENGTHOF(third),
+ *secondUnit,
+ NumberFormat::kDecimalSeparatorField,
+ 0,
+ 0);
+
+ // Fallback to compound per test
+ verifyFieldPositionWithPer(
+ "Integer",
+ fmt,
+ prefix,
+ first,
+ UPRV_LENGTHOF(first),
+ *minuteUnit,
+ NumberFormat::kIntegerField,
+ 8,
+ 11);
+ verifyFieldPositionWithPer(
+ "Decimal separator",
+ fmt,
+ prefix,
+ second,
+ UPRV_LENGTHOF(second),
+ *minuteUnit,
+ NumberFormat::kDecimalSeparatorField,
+ 23,
+ 24);
+ verifyFieldPositionWithPer(
+ "no decimal separator",
+ fmt,
+ prefix,
+ third,
+ UPRV_LENGTHOF(third),
+ *minuteUnit,
+ NumberFormat::kDecimalSeparatorField,
+ 0,
+ 0);
+}
+
void MeasureFormatTest::TestBadArg() {
UErrorCode status = U_ZERO_ERROR;
MeasureFormat fmt("en", UMEASFMT_WIDTH_SHORT, status);
assertEquals(endIndex.data(), end, pos.getEndIndex());
}
+void MeasureFormatTest::verifyFieldPositionWithPer(
+ const char *description,
+ const MeasureFormat &fmt,
+ const UnicodeString &prefix,
+ const Measure *measures,
+ int32_t measureCount,
+ const MeasureUnit &perUnit,
+ NumberFormat::EAlignmentFields field,
+ int32_t start,
+ int32_t end) {
+ UnicodeString result(prefix);
+ FieldPosition pos(field);
+ UErrorCode status = U_ZERO_ERROR;
+ CharString ch;
+ const char *descPrefix = ch.append(description, status)
+ .append(": ", status).data();
+ CharString beginIndex;
+ beginIndex.append(descPrefix, status).append("beginIndex", status);
+ CharString endIndex;
+ endIndex.append(descPrefix, status).append("endIndex", status);
+ fmt.formatMeasuresPer(
+ measures,
+ measureCount,
+ perUnit,
+ result,
+ pos,
+ status);
+ if (!assertSuccess("Error formatting", status)) {
+ return;
+ }
+ assertEquals(beginIndex.data(), start, pos.getBeginIndex());
+ assertEquals(endIndex.data(), end, pos.getEndIndex());
+}
+
void MeasureFormatTest::verifyFormat(
const char *description,
const MeasureFormat &fmt,