From 79964e6bc714d5d1b5f12cdda5fd2d197fbd6d99 Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Fri, 5 Dec 2014 20:52:28 +0000 Subject: [PATCH] ICU-11303 Bring MeasureFormat per unit formatting out of tech preview. X-SVN-Rev: 36813 --- icu4c/source/common/listformatter.cpp | 83 ++--- .../source/common/simplepatternformatter.cpp | 249 +++++++++++-- icu4c/source/common/simplepatternformatter.h | 81 +++-- icu4c/source/i18n/measfmt.cpp | 29 +- icu4c/source/i18n/measunit.cpp | 42 ++- icu4c/source/i18n/quantityformatter.cpp | 8 +- icu4c/source/i18n/unicode/measfmt.h | 25 +- icu4c/source/i18n/unicode/measunit.h | 9 +- icu4c/source/test/intltest/measfmttest.cpp | 334 +++++++----------- .../intltest/simplepatternformattertest.cpp | 323 ++++++++++++++--- 10 files changed, 789 insertions(+), 394 deletions(-) diff --git a/icu4c/source/common/listformatter.cpp b/icu4c/source/common/listformatter.cpp index e4484b17c7..6dbf6c5677 100644 --- a/icu4c/source/common/listformatter.cpp +++ b/icu4c/source/common/listformatter.cpp @@ -248,9 +248,10 @@ ListFormatter::~ListFormatter() { * On entry offset is an offset into first or -1 if offset unspecified. * On exit offset is offset of second in result if recordOffset was set * Otherwise if it was >=0 it is set to point into result where it used - * to point into first. + * to point into first. On exit, result is the join of first and second + * according to pat. Any previous value of result gets replaced. */ -static void joinStrings( +static void joinStringsAndReplace( const SimplePatternFormatter& pat, const UnicodeString& first, const UnicodeString& second, @@ -263,7 +264,7 @@ static void joinStrings( } const UnicodeString *params[2] = {&first, &second}; int32_t offsets[2]; - pat.format( + pat.formatAndReplace( params, UPRV_LENGTHOF(params), result, @@ -319,69 +320,43 @@ UnicodeString& ListFormatter::format( appendTo.append(items[0]); return appendTo; } - if (nItems == 2) { - if (index == 0) { - offset = 0; - } - joinStrings( - data->twoPattern, - items[0], - items[1], - appendTo, - index == 1, - offset, - errorCode); - return appendTo; - } - UnicodeString temp[2]; + UnicodeString result(items[0]); if (index == 0) { offset = 0; } - joinStrings( - data->startPattern, - items[0], + joinStringsAndReplace( + nItems == 2 ? data->twoPattern : data->startPattern, + result, items[1], - temp[0], + result, index == 1, offset, errorCode); - int32_t i; - int32_t pos = 0; - int32_t npos = 0; - UBool startsWithZeroPlaceholder = - data->middlePattern.startsWithPlaceholder(0); - for (i = 2; i < nItems - 1; ++i) { - if (!startsWithZeroPlaceholder) { - npos = (pos + 1) & 1; - temp[npos].remove(); - } - joinStrings( - data->middlePattern, - temp[pos], - items[i], - temp[npos], - index == i, - offset, - errorCode); - pos = npos; + if (nItems > 2) { + for (int32_t i = 2; i < nItems - 1; ++i) { + joinStringsAndReplace( + data->middlePattern, + result, + items[i], + result, + index == i, + offset, + errorCode); + } + joinStringsAndReplace( + data->endPattern, + result, + items[nItems - 1], + result, + index == nItems - 1, + offset, + errorCode); } - if (!data->endPattern.startsWithPlaceholder(0)) { - npos = (pos + 1) & 1; - temp[npos].remove(); - } - joinStrings( - data->endPattern, - temp[pos], - items[nItems - 1], - temp[npos], - index == nItems - 1, - offset, - errorCode); if (U_SUCCESS(errorCode)) { if (offset >= 0) { offset += appendTo.length(); } - appendTo += temp[npos]; + appendTo += result; } return appendTo; } diff --git a/icu4c/source/common/simplepatternformatter.cpp b/icu4c/source/common/simplepatternformatter.cpp index 30390f4150..0cac2ec3fd 100644 --- a/icu4c/source/common/simplepatternformatter.cpp +++ b/icu4c/source/common/simplepatternformatter.cpp @@ -11,20 +11,38 @@ U_NAMESPACE_BEGIN +static UBool isInvalidArray(const void *array, int32_t size) { + return (size < 0 || (size > 0 && array == NULL)); +} + typedef enum SimplePatternFormatterCompileState { INIT, APOSTROPHE, PLACEHOLDER } SimplePatternFormatterCompileState; +// Handles parsing placeholders in the pattern string, e.g {4} or {35} class SimplePatternFormatterIdBuilder { public: SimplePatternFormatterIdBuilder() : id(0), idLen(0) { } ~SimplePatternFormatterIdBuilder() { } + + // Resets so that this object has seen no placeholder ID. void reset() { id = 0; idLen = 0; } + + // Returns the numeric placeholder ID parsed so far int32_t getId() const { return id; } + + // Appends the numeric placeholder ID parsed so far back to a + // UChar buffer. Used to recover if parser using this object finds + // no closing curly brace. void appendTo(UChar *buffer, int32_t *len) const; + + // Returns true if this object has seen a placeholder ID. UBool isValid() const { return (idLen > 0); } + + // Processes a single digit character. Pattern string parser calls this + // as it processes digits after an opening curly brace. void add(UChar ch); private: int32_t id; @@ -52,18 +70,81 @@ void SimplePatternFormatterIdBuilder::add(UChar ch) { idLen++; } +// Represents placeholder values. +class SimplePatternFormatterPlaceholderValues : public UMemory { +public: + SimplePatternFormatterPlaceholderValues( + const UnicodeString * const *values, + int32_t valuesCount); + + // Returns TRUE if appendTo value is at any index besides exceptIndex. + UBool isAppendToInAnyIndexExcept( + const UnicodeString &appendTo, int32_t exceptIndex) const; + + // For each appendTo value, stores the snapshot of it in its place. + void snapshotAppendTo(const UnicodeString &appendTo); + + // Returns the placeholder value at index. No range checking performed. + // Returned reference is valid for as long as this object exists. + const UnicodeString &get(int32_t index) const; +private: + const UnicodeString * const *fValues; + int32_t fValuesCount; + const UnicodeString *fAppendTo; + UnicodeString fAppendToCopy; + SimplePatternFormatterPlaceholderValues( + const SimplePatternFormatterPlaceholderValues &); + SimplePatternFormatterPlaceholderValues &operator=( + const SimplePatternFormatterPlaceholderValues &); +}; + +SimplePatternFormatterPlaceholderValues::SimplePatternFormatterPlaceholderValues( + const UnicodeString * const *values, + int32_t valuesCount) + : fValues(values), + fValuesCount(valuesCount), + fAppendTo(NULL), + fAppendToCopy() { +} + +UBool SimplePatternFormatterPlaceholderValues::isAppendToInAnyIndexExcept( + const UnicodeString &appendTo, int32_t exceptIndex) const { + for (int32_t i = 0; i < fValuesCount; ++i) { + if (i != exceptIndex && fValues[i] == &appendTo) { + return TRUE; + } + } + return FALSE; +} + +void SimplePatternFormatterPlaceholderValues::snapshotAppendTo( + const UnicodeString &appendTo) { + fAppendTo = &appendTo; + fAppendToCopy = appendTo; +} + +const UnicodeString &SimplePatternFormatterPlaceholderValues::get( + int32_t index) const { + if (fAppendTo == NULL || fAppendTo != fValues[index]) { + return *fValues[index]; + } + return fAppendToCopy; +} + SimplePatternFormatter::SimplePatternFormatter() : noPlaceholders(), placeholders(), placeholderSize(0), - placeholderCount(0) { + placeholderCount(0), + firstPlaceholderReused(FALSE) { } SimplePatternFormatter::SimplePatternFormatter(const UnicodeString &pattern) : noPlaceholders(), placeholders(), placeholderSize(0), - placeholderCount(0) { + placeholderCount(0), + firstPlaceholderReused(FALSE) { UErrorCode status = U_ZERO_ERROR; compile(pattern, status); } @@ -73,7 +154,8 @@ SimplePatternFormatter::SimplePatternFormatter( noPlaceholders(other.noPlaceholders), placeholders(), placeholderSize(0), - placeholderCount(other.placeholderCount) { + placeholderCount(other.placeholderCount), + firstPlaceholderReused(other.firstPlaceholderReused) { placeholderSize = ensureCapacity(other.placeholderSize); uprv_memcpy( placeholders.getAlias(), @@ -89,6 +171,7 @@ SimplePatternFormatter &SimplePatternFormatter::operator=( noPlaceholders = other.noPlaceholders; placeholderSize = ensureCapacity(other.placeholderSize); placeholderCount = other.placeholderCount; + firstPlaceholderReused = other.firstPlaceholderReused; uprv_memcpy( placeholders.getAlias(), other.placeholders.getAlias(), @@ -175,19 +258,12 @@ UBool SimplePatternFormatter::compile( return TRUE; } -UBool SimplePatternFormatter::startsWithPlaceholder(int32_t id) const { - if (placeholderSize == 0) { - return FALSE; - } - return (placeholders[0].offset == 0 && placeholders[0].id == id); -} - UnicodeString& SimplePatternFormatter::format( const UnicodeString &arg0, UnicodeString &appendTo, UErrorCode &status) const { const UnicodeString *params[] = {&arg0}; - return format( + return formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -202,7 +278,7 @@ UnicodeString& SimplePatternFormatter::format( UnicodeString &appendTo, UErrorCode &status) const { const UnicodeString *params[] = {&arg0, &arg1}; - return format( + return formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -218,7 +294,7 @@ UnicodeString& SimplePatternFormatter::format( UnicodeString &appendTo, UErrorCode &status) const { const UnicodeString *params[] = {&arg0, &arg1, &arg2}; - return format( + return formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -242,10 +318,14 @@ static void appendRange( int32_t start, int32_t end, UnicodeString &dest) { + // This check improves performance significantly. + if (start == end) { + return; + } dest.append(src, start, end - start); } -UnicodeString& SimplePatternFormatter::format( +UnicodeString& SimplePatternFormatter::formatAndAppend( const UnicodeString * const *placeholderValues, int32_t placeholderValueCount, UnicodeString &appendTo, @@ -255,10 +335,102 @@ UnicodeString& SimplePatternFormatter::format( if (U_FAILURE(status)) { return appendTo; } + if (isInvalidArray(placeholderValues, placeholderValueCount) + || isInvalidArray(offsetArray, offsetArrayLength)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return appendTo; + } if (placeholderValueCount < placeholderCount) { status = U_ILLEGAL_ARGUMENT_ERROR; return appendTo; } + + // Since we are disallowing parameter values that are the same as + // appendTo, we have to check all placeholderValues as opposed to + // the first placeholderCount placeholder values. + SimplePatternFormatterPlaceholderValues values( + placeholderValues, placeholderValueCount); + if (values.isAppendToInAnyIndexExcept(appendTo, -1)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return appendTo; + } + return formatAndAppend( + values, + appendTo, + offsetArray, + offsetArrayLength); +} + +UnicodeString& SimplePatternFormatter::formatAndReplace( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &result, + int32_t *offsetArray, + int32_t offsetArrayLength, + UErrorCode &status) const { + if (U_FAILURE(status)) { + return result; + } + if (isInvalidArray(placeholderValues, placeholderValueCount) + || isInvalidArray(offsetArray, offsetArrayLength)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return result; + } + if (placeholderValueCount < placeholderCount) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return result; + } + SimplePatternFormatterPlaceholderValues values( + placeholderValues, placeholderCount); + int32_t placeholderAtStart = getUniquePlaceholderAtStart(); + + // If pattern starts with a unique placeholder and that placeholder + // value is result, we may be able to optimize by just appending to result. + if (placeholderAtStart >= 0 + && placeholderValues[placeholderAtStart] == &result) { + + // If result is the value for other placeholders, call off optimization. + if (values.isAppendToInAnyIndexExcept(result, placeholderAtStart)) { + values.snapshotAppendTo(result); + result.remove(); + return formatAndAppend( + values, + result, + offsetArray, + offsetArrayLength); + } + + // Otherwise we can optimize + formatAndAppend( + values, + result, + offsetArray, + offsetArrayLength); + + // We have to make the offset for the placeholderAtStart + // placeholder be 0. Otherwise it would be the length of the + // previous value of result. + if (offsetArrayLength > placeholderAtStart) { + offsetArray[placeholderAtStart] = 0; + } + return result; + } + if (values.isAppendToInAnyIndexExcept(result, -1)) { + values.snapshotAppendTo(result); + } + result.remove(); + return formatAndAppend( + values, + result, + offsetArray, + offsetArrayLength); +} + +UnicodeString& SimplePatternFormatter::formatAndAppend( + const SimplePatternFormatterPlaceholderValues &values, + UnicodeString &appendTo, + int32_t *offsetArray, + int32_t offsetArrayLength) const { for (int32_t i = 0; i < offsetArrayLength; ++i) { offsetArray[i] = -1; } @@ -266,25 +438,19 @@ UnicodeString& SimplePatternFormatter::format( appendTo.append(noPlaceholders); return appendTo; } - if (placeholders[0].offset > 0 || - placeholderValues[placeholders[0].id] != &appendTo) { - appendRange( - noPlaceholders, - 0, - placeholders[0].offset, - appendTo); - updatePlaceholderOffset( - placeholders[0].id, - appendTo.length(), - offsetArray, - offsetArrayLength); - appendTo.append(*placeholderValues[placeholders[0].id]); - } else { - updatePlaceholderOffset( - placeholders[0].id, - 0, - offsetArray, - offsetArrayLength); + appendRange( + noPlaceholders, + 0, + placeholders[0].offset, + appendTo); + updatePlaceholderOffset( + placeholders[0].id, + appendTo.length(), + offsetArray, + offsetArrayLength); + const UnicodeString *placeholderValue = &values.get(placeholders[0].id); + if (placeholderValue != &appendTo) { + appendTo.append(*placeholderValue); } for (int32_t i = 1; i < placeholderSize; ++i) { appendRange( @@ -297,7 +463,10 @@ UnicodeString& SimplePatternFormatter::format( appendTo.length(), offsetArray, offsetArrayLength); - appendTo.append(*placeholderValues[placeholders[i].id]); + placeholderValue = &values.get(placeholders[i].id); + if (placeholderValue != &appendTo) { + appendTo.append(*placeholderValue); + } } appendRange( noPlaceholders, @@ -307,6 +476,14 @@ UnicodeString& SimplePatternFormatter::format( return appendTo; } +int32_t SimplePatternFormatter::getUniquePlaceholderAtStart() const { + if (placeholderSize == 0 + || firstPlaceholderReused || placeholders[0].offset != 0) { + return -1; + } + return placeholders[0].id; +} + int32_t SimplePatternFormatter::ensureCapacity( int32_t desiredCapacity, int32_t allocationSize) { if (allocationSize < desiredCapacity) { @@ -333,6 +510,10 @@ UBool SimplePatternFormatter::addPlaceholder(int32_t id, int32_t offset) { if (id >= placeholderCount) { placeholderCount = id + 1; } + if (placeholderSize > 1 + && placeholders[placeholderSize - 1].id == placeholders[0].id) { + firstPlaceholderReused = TRUE; + } return TRUE; } diff --git a/icu4c/source/common/simplepatternformatter.h b/icu4c/source/common/simplepatternformatter.h index b286a79cc3..6740dc9f47 100644 --- a/icu4c/source/common/simplepatternformatter.h +++ b/icu4c/source/common/simplepatternformatter.h @@ -17,6 +17,8 @@ U_NAMESPACE_BEGIN +class SimplePatternFormatterPlaceholderValues; + struct PlaceholderInfo { int32_t id; int32_t offset; @@ -39,7 +41,7 @@ struct PlaceholderInfo { * UnicodeString result; * UErrorCode status = U_ZERO_ERROR; * // Evaluates to: "paul {born} in england" - * fmt.format("englad", "paul", result, status); + * fmt.format("england", "paul", result, status); * */ class U_COMMON_API SimplePatternFormatter : public UMemory { @@ -90,12 +92,6 @@ public: return placeholderCount; } - /** - * Returns true if the pattern this object represents starts with - * placeholder id; otherwise, returns false. - */ - UBool startsWithPlaceholder(int32_t id) const; - /** * Returns this pattern with none of the placeholders. */ @@ -104,7 +100,7 @@ public: } /** - * Formats given value. + * Formats given value. arg0 cannot be appendTo. */ UnicodeString &format( const UnicodeString &args0, @@ -112,7 +108,7 @@ public: UErrorCode &status) const; /** - * Formats given values. + * Formats given values. Neither arg0 nor arg1 can be appendTo. */ UnicodeString &format( const UnicodeString &args0, @@ -121,7 +117,7 @@ public: UErrorCode &status) const; /** - * Formats given values. + * Formats given values. Neither arg0, arg1, nor arg2 can be appendTo. */ UnicodeString &format( const UnicodeString &args0, @@ -135,37 +131,82 @@ public: * * The caller retains ownership of all pointers. * @param placeholderValues 1st one corresponds to {0}; 2nd to {1}; - * 3rd to {2} etc. + * 3rd to {2} etc. If any of these point to appendTo, this method + * sets status to U_ILLEGAL_ARGUMENT_ERROR. * @param placeholderValueCount the number of placeholder values * must be at least large enough to provide values for all placeholders * in this object. Otherwise status set to U_ILLEGAL_ARGUMENT_ERROR. - * @param appendTo resulting string appended here. Optimization: If - * the pattern this object represents starts with a placeholder AND - * appendTo references the value of that same placeholder, then that - * placeholder value is not copied to appendTo (Its already there). - * If the value of the starting placeholder is a very large string, - * this optimization can offer huge savings. + * @param appendTo resulting string appended here. * @param offsetArray The offset of each placeholder value in appendTo * stored here. The first value gets the offset of the value for {0}; * the 2nd for {1}; the 3rd for {2} etc. -1 means that the corresponding * placeholder does not exist in this object. If caller is not * interested in offsets, it may pass NULL and 0 for the length. - * @param offsetArrayLength the size of offsetArray may be less than - * placeholderValueCount. + * @param offsetArrayLength the size of offsetArray. If less than + * placeholderValueCount only the first offsets get recorded. If + * greater than placeholderValueCount, then extra values in offset + * array are set to -1. * @param status any error stored here. */ - UnicodeString &format( + UnicodeString &formatAndAppend( const UnicodeString * const *placeholderValues, int32_t placeholderValueCount, UnicodeString &appendTo, int32_t *offsetArray, int32_t offsetArrayLength, UErrorCode &status) const; + + /** + * Formats given values. + * + * The caller retains ownership of all pointers. + * @param placeholderValues 1st one corresponds to {0}; 2nd to {1}; + * 3rd to {2} etc. May include pointer to result in which case + * the previous value of result is used for the corresponding + * placeholder. + * @param placeholderValueCount the number of placeholder values + * must be at least large enough to provide values for all placeholders + * in this object. Otherwise status set to U_ILLEGAL_ARGUMENT_ERROR. + * @param result resulting string stored here overwriting any previous + * value. + * @param offsetArray The offset of each placeholder value in result + * stored here. The first value gets the offset of the value for {0}; + * the 2nd for {1}; the 3rd for {2} etc. -1 means that the corresponding + * placeholder does not exist in this object. If caller is not + * interested in offsets, it may pass NULL and 0 for the length. + * @param offsetArrayLength the size of offsetArray. If less than + * placeholderValueCount only the first offsets get recorded. If + * greater than placeholderValueCount, then extra values in offset + * array are set to -1. + * @param status any error stored here. + */ + UnicodeString &formatAndReplace( + const UnicodeString * const *placeholderValues, + int32_t placeholderValueCount, + UnicodeString &result, + int32_t *offsetArray, + int32_t offsetArrayLength, + UErrorCode &status) const; private: UnicodeString noPlaceholders; MaybeStackArray placeholders; int32_t placeholderSize; int32_t placeholderCount; + UBool firstPlaceholderReused; + + // A Placeholder value that is the same as appendTo is treated as the + // empty string. + UnicodeString &formatAndAppend( + const SimplePatternFormatterPlaceholderValues &placeholderValues, + UnicodeString &appendTo, + int32_t *offsetArray, + int32_t offsetArrayLength) const; + + // Returns the placeholder at the beginning of this pattern + // (e.g 3 for placeholder {3}). Returns -1 if the beginning of pattern + // is text or if the placeholder at the beginning of this pattern + // is used again in the middle of the pattern. + int32_t getUniquePlaceholderAtStart() const; // ensureCapacity ensures that the capacity of the placeholders array // is desiredCapacity. If ensureCapacity must resize the placeholders diff --git a/icu4c/source/i18n/measfmt.cpp b/icu4c/source/i18n/measfmt.cpp index 017200c85d..45a55c29e0 100644 --- a/icu4c/source/i18n/measfmt.cpp +++ b/icu4c/source/i18n/measfmt.cpp @@ -590,18 +590,27 @@ void MeasureFormat::parseObject( return; } -UnicodeString &MeasureFormat::formatMeasuresPer( - const Measure *measures, - int32_t measureCount, +UnicodeString &MeasureFormat::formatMeasurePerUnit( + const Measure &measure, const MeasureUnit &perUnit, UnicodeString &appendTo, FieldPosition &pos, UErrorCode &status) const { + if (U_FAILURE(status)) { + return appendTo; + } + MeasureUnit *resolvedUnit = + MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit); + if (resolvedUnit != NULL) { + Measure newMeasure(measure.getNumber(), resolvedUnit, status); + return formatMeasure( + newMeasure, **numberFormat, appendTo, pos, status); + } FieldPosition fpos(pos.getField()); - UnicodeString measuresString; - int32_t offset = withPerUnit( - formatMeasures( - measures, measureCount, measuresString, fpos, status), + UnicodeString result; + int32_t offset = withPerUnitAndAppend( + formatMeasure( + measure, **numberFormat, result, fpos, status), perUnit, appendTo, status); @@ -979,7 +988,7 @@ static void getPerUnitString( result.trim(); } -int32_t MeasureFormat::withPerUnit( +int32_t MeasureFormat::withPerUnitAndAppend( const UnicodeString &formatted, const MeasureUnit &perUnit, UnicodeString &appendTo, @@ -992,7 +1001,7 @@ int32_t MeasureFormat::withPerUnit( perUnit.getIndex(), widthToIndex(width)); if (perUnitFormatter != NULL) { const UnicodeString *params[] = {&formatted}; - perUnitFormatter->format( + perUnitFormatter->formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -1011,7 +1020,7 @@ int32_t MeasureFormat::withPerUnit( UnicodeString perUnitString; getPerUnitString(*qf, perUnitString); const UnicodeString *params[] = {&formatted, &perUnitString}; - perFormatter->format( + perFormatter->formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp index 73ba0ac55e..58d0e59c71 100644 --- a/icu4c/source/i18n/measunit.cpp +++ b/icu4c/source/i18n/measunit.cpp @@ -27,7 +27,7 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MeasureUnit) // the "End generated code" comment is auto generated code // and must not be edited manually. For instructions on how to correctly // update this code, refer to: -// https://sites.google.com/site/icusite/design/formatting/measureformat/updating-measure-unit +// http://site.icu-project.org/design/formatting/measureformat/updating-measure-unit // // Start generated code @@ -77,6 +77,7 @@ static const int32_t gIndexes[] = { 121 }; +// Must be sorted alphabetically. static const char * const gTypes[] = { "acceleration", "angle", @@ -99,6 +100,7 @@ static const char * const gTypes[] = { "volume" }; +// Must be grouped by type and sorted alphabetically within each type. static const char * const gSubTypes[] = { "g-force", "meter-per-second-squared", @@ -483,6 +485,16 @@ static const char * const gSubTypes[] = { "teaspoon" }; +// Must be sorted by first value and then second value. +static int32_t unitPerUnitToSingleUnit[][4] = { + {318, 288, 16, 0}, + {320, 294, 16, 1}, + {322, 288, 16, 2}, + {322, 372, 3, 1}, + {338, 10, 14, 4}, + {374, 318, 3, 0} +}; + MeasureUnit *MeasureUnit::createGForce(UErrorCode &status) { return MeasureUnit::create(0, 0, status); } @@ -1100,6 +1112,34 @@ int32_t MeasureUnit::getIndexCount() { return gIndexes[UPRV_LENGTHOF(gIndexes) - 1]; } +MeasureUnit *MeasureUnit::resolveUnitPerUnit( + const MeasureUnit &unit, const MeasureUnit &perUnit) { + int32_t unitOffset = unit.getOffset(); + int32_t perUnitOffset = perUnit.getOffset(); + + // binary search for (unitOffset, perUnitOffset) + int32_t start = 0; + int32_t end = UPRV_LENGTHOF(unitPerUnitToSingleUnit); + while (start < end) { + int32_t mid = (start + end) / 2; + int32_t *midRow = unitPerUnitToSingleUnit[mid]; + if (unitOffset < midRow[0]) { + end = mid; + } else if (unitOffset > midRow[0]) { + start = mid + 1; + } else if (perUnitOffset < midRow[1]) { + end = mid; + } else if (perUnitOffset > midRow[1]) { + start = mid + 1; + } else { + // We found a resolution for our unit / per-unit combo + // return it. + return new MeasureUnit(midRow[2], midRow[3]); + } + } + return NULL; +} + MeasureUnit *MeasureUnit::create(int typeId, int subTypeId, UErrorCode &status) { if (U_FAILURE(status)) { return NULL; diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp index bef509a718..d0dc378bd9 100644 --- a/icu4c/source/i18n/quantityformatter.cpp +++ b/icu4c/source/i18n/quantityformatter.cpp @@ -170,7 +170,13 @@ UnicodeString &QuantityFormatter::format( fmt.format(quantity, formattedNumber, fpos, status); const UnicodeString *params[1] = {&formattedNumber}; int32_t offsets[1]; - pattern->format(params, UPRV_LENGTHOF(params), appendTo, offsets, UPRV_LENGTHOF(offsets), status); + pattern->formatAndAppend( + params, + UPRV_LENGTHOF(params), + appendTo, + offsets, + UPRV_LENGTHOF(offsets), + status); if (offsets[0] != -1) { if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { pos.setBeginIndex(fpos.getBeginIndex() + offsets[0]); diff --git a/icu4c/source/i18n/unicode/measfmt.h b/icu4c/source/i18n/unicode/measfmt.h index 91e08ef8d8..2eab7ebfcd 100644 --- a/icu4c/source/i18n/unicode/measfmt.h +++ b/icu4c/source/i18n/unicode/measfmt.h @@ -190,31 +190,28 @@ class U_I18N_API MeasureFormat : public Format { UnicodeString &appendTo, FieldPosition &pos, 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). + * Formats a single measure per unit. An example of such a + * formatted string is 3.5 meters per second. + * @param measure The measure object. In above example, 3.5 meters. + * @param perUnit The per unit. In above example, it is + * *MeasureUnit::createSecond(status). * @param appendTo formatted string appended here. * @param pos the field position. * @param status the error. * @return appendTo reference * - * @internal Technology preview + * @draft ICU 55 */ - UnicodeString &formatMeasuresPer( - const Measure *measures, - int32_t measureCount, + UnicodeString &formatMeasurePerUnit( + const Measure &measure, const MeasureUnit &perUnit, UnicodeString &appendTo, FieldPosition &pos, UErrorCode &status) const; -#endif /* U_HIDE_INTERNAL_API */ + +#endif /* U_HIDE_DRAFT_API */ /** * Return a formatter for CurrencyAmount objects in the given @@ -347,7 +344,7 @@ class U_I18N_API MeasureFormat : public Format { int32_t widthIndex, UErrorCode &status) const; - int32_t withPerUnit( + int32_t withPerUnitAndAppend( const UnicodeString &formatted, const MeasureUnit &perUnit, UnicodeString &appendTo, diff --git a/icu4c/source/i18n/unicode/measunit.h b/icu4c/source/i18n/unicode/measunit.h index 442f4a3e22..446b3ee992 100644 --- a/icu4c/source/i18n/unicode/measunit.h +++ b/icu4c/source/i18n/unicode/measunit.h @@ -184,13 +184,20 @@ class U_I18N_API MeasureUnit: public UObject { * @internal */ static int32_t getIndexCount(); + + /** + * ICU use only. + * @internal + */ + static MeasureUnit *resolveUnitPerUnit( + const MeasureUnit &unit, const MeasureUnit &perUnit); #endif /* U_HIDE_INTERNAL_API */ // All code between the "Start generated createXXX methods" comment and // the "End generated createXXX methods" comment is auto generated code // and must not be edited manually. For instructions on how to correctly // update this code, refer to: -// https://sites.google.com/site/icusite/design/formatting/measureformat/updating-measure-unit +// http://site.icu-project.org/design/formatting/measureformat/updating-measure-unit // // Start generated createXXX methods diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp index ccbc75729a..b2becbf3cb 100644 --- a/icu4c/source/test/intltest/measfmttest.cpp +++ b/icu4c/source/test/intltest/measfmttest.cpp @@ -47,7 +47,6 @@ private: void TestGreek(); void TestFormatSingleArg(); void TestFormatMeasuresZeroArg(); - void TestMultiplesWithPer(); void TestSimplePer(); void TestNumeratorPlurals(); void TestMultiples(); @@ -55,11 +54,11 @@ private: void TestCurrencies(); void TestFieldPosition(); void TestFieldPositionMultiple(); - void TestFieldPositionMultipleWithPer(); void TestBadArg(); void TestEquality(); void TestGroupingSeparator(); void TestDoubleZero(); + void TestUnitPerUnitResolution(); void verifyFormat( const char *description, const MeasureFormat &fmt, @@ -85,11 +84,16 @@ private: const MeasureUnit &unit, const MeasureUnit &perUnit, const char *expected); - void helperTestMultiplesWithPer( + void helperTestSimplePer( const Locale &locale, UMeasureFormatWidth width, + double value, const MeasureUnit &unit, - const char *expected); + const MeasureUnit &perUnit, + const char *expected, + int32_t field, + int32_t expected_start, + int32_t expected_end); void helperTestMultiples( const Locale &locale, UMeasureFormatWidth width, @@ -103,16 +107,6 @@ private: 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( @@ -131,7 +125,6 @@ void MeasureFormatTest::runIndexedTest( TESTCASE_AUTO(TestGreek); TESTCASE_AUTO(TestFormatSingleArg); TESTCASE_AUTO(TestFormatMeasuresZeroArg); - TESTCASE_AUTO(TestMultiplesWithPer); TESTCASE_AUTO(TestSimplePer); TESTCASE_AUTO(TestNumeratorPlurals); TESTCASE_AUTO(TestMultiples); @@ -139,11 +132,11 @@ void MeasureFormatTest::runIndexedTest( TESTCASE_AUTO(TestCurrencies); TESTCASE_AUTO(TestFieldPosition); TESTCASE_AUTO(TestFieldPositionMultiple); - TESTCASE_AUTO(TestFieldPositionMultipleWithPer); TESTCASE_AUTO(TestBadArg); TESTCASE_AUTO(TestEquality); TESTCASE_AUTO(TestGroupingSeparator); TESTCASE_AUTO(TestDoubleZero); + TESTCASE_AUTO(TestUnitPerUnitResolution); TESTCASE_AUTO_END; } @@ -871,32 +864,6 @@ void MeasureFormatTest::TestFormatMeasuresZeroArg() { verifyFormat("TestFormatMeasuresZeroArg", fmt, NULL, 0, ""); } -void MeasureFormatTest::TestMultiplesWithPer() { - Locale en("en"); - UErrorCode status = U_ZERO_ERROR; - LocalPointer second(MeasureUnit::createSecond(status)); - LocalPointer 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::TestSimplePer() { Locale en("en"); UErrorCode status = U_ZERO_ERROR; @@ -907,6 +874,19 @@ void MeasureFormatTest::TestSimplePer() { return; } + helperTestSimplePer( + en, UMEASFMT_WIDTH_WIDE, + 1.0, *pound, *second, "1 pound per second"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_WIDE, + 2.0, *pound, *second, "2 pounds per second"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_WIDE, + 1.0, *pound, *minute, "1 pound per minute"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_WIDE, + 2.0, *pound, *minute, "2 pounds per minute"); + helperTestSimplePer( en, UMEASFMT_WIDTH_SHORT, 1.0, *pound, *second, "1 lbps"); helperTestSimplePer( @@ -915,6 +895,39 @@ void MeasureFormatTest::TestSimplePer() { en, UMEASFMT_WIDTH_SHORT, 1.0, *pound, *minute, "1 lb/min"); helperTestSimplePer( en, UMEASFMT_WIDTH_SHORT, 2.0, *pound, *minute, "2 lb/min"); + + helperTestSimplePer( + en, UMEASFMT_WIDTH_NARROW, 1.0, *pound, *second, "1#/s"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_NARROW, 2.0, *pound, *second, "2#/s"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_NARROW, 1.0, *pound, *minute, "1#/m"); + helperTestSimplePer( + en, UMEASFMT_WIDTH_NARROW, 2.0, *pound, *minute, "2#/m"); + + helperTestSimplePer( + en, UMEASFMT_WIDTH_SHORT, + 23.3, *pound, *second, "23.3 lbps", + NumberFormat::kDecimalSeparatorField, + 2, 3); + + helperTestSimplePer( + en, UMEASFMT_WIDTH_SHORT, + 23.3, *pound, *second, "23.3 lbps", + NumberFormat::kIntegerField, + 0, 2); + + helperTestSimplePer( + en, UMEASFMT_WIDTH_SHORT, + 23.3, *pound, *minute, "23.3 lb/min", + NumberFormat::kDecimalSeparatorField, + 2, 3); + + helperTestSimplePer( + en, UMEASFMT_WIDTH_SHORT, + 23.3, *pound, *minute, "23.3 lb/min", + NumberFormat::kIntegerField, + 0, 2); } void MeasureFormatTest::TestNumeratorPlurals() { @@ -927,13 +940,20 @@ void MeasureFormatTest::TestNumeratorPlurals() { } helperTestSimplePer( - pl, UMEASFMT_WIDTH_WIDE, 1.0, *foot, *second, "1 stopa na sekund\\u0119"); + pl, + UMEASFMT_WIDTH_WIDE, + 1.0, *foot, *second, "1 stopa na sekund\\u0119"); helperTestSimplePer( - pl, UMEASFMT_WIDTH_WIDE, 2.0, *foot, *second, "2 stopy na sekund\\u0119"); + pl, + UMEASFMT_WIDTH_WIDE, + 2.0, *foot, *second, "2 stopy na sekund\\u0119"); helperTestSimplePer( - pl, UMEASFMT_WIDTH_WIDE, 5.0, *foot, *second, "5 st\\u00f3p na sekund\\u0119"); + pl, + UMEASFMT_WIDTH_WIDE, + 5.0, *foot, *second, "5 st\\u00f3p na sekund\\u0119"); helperTestSimplePer( - pl, UMEASFMT_WIDTH_WIDE, 1.5, *foot, *second, "1,5 stopy na sekund\\u0119"); + pl, UMEASFMT_WIDTH_WIDE, + 1.5, *foot, *second, "1,5 stopy na sekund\\u0119"); } void MeasureFormatTest::helperTestSimplePer( @@ -943,8 +963,30 @@ void MeasureFormatTest::helperTestSimplePer( const MeasureUnit &unit, const MeasureUnit &perUnit, const char *expected) { + helperTestSimplePer( + locale, + width, + value, + unit, + perUnit, + expected, + FieldPosition::DONT_CARE, + 0, + 0); +} + +void MeasureFormatTest::helperTestSimplePer( + const Locale &locale, + UMeasureFormatWidth width, + double value, + const MeasureUnit &unit, + const MeasureUnit &perUnit, + const char *expected, + int32_t field, + int32_t expected_start, + int32_t expected_end) { UErrorCode status = U_ZERO_ERROR; - FieldPosition pos(0); + FieldPosition pos(field); MeasureFormat fmt(locale, width, status); if (!assertSuccess("Error creating format object", status)) { return; @@ -953,10 +995,10 @@ void MeasureFormatTest::helperTestSimplePer( if (!assertSuccess("Error creating measure object", status)) { return; } - UnicodeString buffer; - fmt.formatMeasuresPer( - &measure, - 1, + UnicodeString prefix("prefix: "); + UnicodeString buffer(prefix); + fmt.formatMeasurePerUnit( + measure, perUnit, buffer, pos, @@ -964,45 +1006,18 @@ void MeasureFormatTest::helperTestSimplePer( if (!assertSuccess("Error formatting measures with per", status)) { return; } + UnicodeString uexpected(expected); + uexpected = prefix + uexpected; assertEquals( "TestSimplePer", - UnicodeString(expected).unescape(), + uexpected.unescape(), buffer); -} - -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; + if (field != FieldPosition::DONT_CARE) { + assertEquals( + "Start", expected_start, pos.getBeginIndex() - prefix.length()); + assertEquals( + "End", expected_end, pos.getEndIndex() - prefix.length()); } - 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() { @@ -1181,99 +1196,6 @@ void MeasureFormatTest::TestFieldPositionMultiple() { 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 secondUnit(MeasureUnit::createSecond(status)); - LocalPointer 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); @@ -1379,6 +1301,26 @@ void MeasureFormatTest::TestDoubleZero() { appendTo); } +void MeasureFormatTest::TestUnitPerUnitResolution() { + UErrorCode status = U_ZERO_ERROR; + Locale en("en"); + MeasureFormat fmt("en", UMEASFMT_WIDTH_SHORT, status); + Measure measure(50, MeasureUnit::createPound(status), status); + LocalPointer sqInch(MeasureUnit::createSquareInch(status)); + if (!assertSuccess("Create of format unit and per unit", status)) { + return; + } + FieldPosition pos(0); + UnicodeString actual; + fmt.formatMeasurePerUnit( + measure, + *sqInch, + actual, + pos, + status); + assertEquals("", "50 psi", actual); +} + void MeasureFormatTest::verifyFieldPosition( const char *description, const MeasureFormat &fmt, @@ -1407,40 +1349,6 @@ void MeasureFormatTest::verifyFieldPosition( 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, diff --git a/icu4c/source/test/intltest/simplepatternformattertest.cpp b/icu4c/source/test/intltest/simplepatternformattertest.cpp index a530c52291..4c08af01ae 100644 --- a/icu4c/source/test/intltest/simplepatternformattertest.cpp +++ b/icu4c/source/test/intltest/simplepatternformattertest.cpp @@ -19,10 +19,21 @@ public: void TestNoPlaceholders(); void TestOnePlaceholder(); void TestManyPlaceholders(); + void TestTooFewPlaceholderValues(); + void TestBadArguments(); void TestGetPatternWithNoPlaceholders(); - void TestOptimization(); + void TestFormatReplaceNoOptimization(); + void TestFormatReplaceNoOptimizationLeadingText(); + void TestFormatReplaceOptimization(); + void TestFormatReplaceNoOptimizationLeadingPlaceholderUsedTwice(); + void TestFormatReplaceOptimizationNoOffsets(); + void TestFormatReplaceNoOptimizationNoOffsets(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0); private: + void verifyOffsets( + const int32_t *expected, + const int32_t *actual, + int32_t count); }; void SimplePatternFormatterTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) { @@ -30,8 +41,15 @@ void SimplePatternFormatterTest::runIndexedTest(int32_t index, UBool exec, const TESTCASE_AUTO(TestNoPlaceholders); TESTCASE_AUTO(TestOnePlaceholder); TESTCASE_AUTO(TestManyPlaceholders); + TESTCASE_AUTO(TestTooFewPlaceholderValues); + TESTCASE_AUTO(TestBadArguments); TESTCASE_AUTO(TestGetPatternWithNoPlaceholders); - TESTCASE_AUTO(TestOptimization); + TESTCASE_AUTO(TestFormatReplaceNoOptimization); + TESTCASE_AUTO(TestFormatReplaceNoOptimizationLeadingText); + TESTCASE_AUTO(TestFormatReplaceOptimization); + TESTCASE_AUTO(TestFormatReplaceNoOptimizationLeadingPlaceholderUsedTwice); + TESTCASE_AUTO(TestFormatReplaceOptimizationNoOffsets); + TESTCASE_AUTO(TestFormatReplaceNoOptimizationNoOffsets); TESTCASE_AUTO_END; } @@ -58,13 +76,15 @@ void SimplePatternFormatterTest::TestOnePlaceholder() { UErrorCode status = U_ZERO_ERROR; SimplePatternFormatter fmt; fmt.compile("{0} meter", status); + if (!assertSuccess("Status", status)) { + return; + } assertEquals("PlaceholderCount", 1, fmt.getPlaceholderCount()); UnicodeString appendTo; assertEquals( "format", "1 meter", fmt.format("1", appendTo, status)); - assertSuccess("Status", status); // assignment SimplePatternFormatter s; @@ -90,8 +110,9 @@ void SimplePatternFormatterTest::TestManyPlaceholders() { SimplePatternFormatter fmt; fmt.compile( "Templates {2}{1}{5} and {4} are out of order.", status); - assertSuccess("Status", status); - assertFalse("startsWithPlaceholder", fmt.startsWithPlaceholder(2)); + if (!assertSuccess("Status", status)) { + return; + } assertEquals("PlaceholderCount", 6, fmt.getPlaceholderCount()); UnicodeString values[] = { "freddy", "tommy", "frog", "billy", "leg", "{0}"}; @@ -103,38 +124,24 @@ void SimplePatternFormatterTest::TestManyPlaceholders() { assertEquals( "format", "Prefix: Templates frogtommy{0} and leg are out of order.", - fmt.format( + fmt.formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, offsets, UPRV_LENGTHOF(offsets), status)); - assertSuccess("Status", status); - for (int32_t i = 0; i < UPRV_LENGTHOF(expectedOffsets); ++i) { - if (expectedOffsets[i] != offsets[i]) { - errln("Expected %d, got %d", expectedOffsets[i], offsets[i]); - } + if (!assertSuccess("Status", status)) { + return; } + verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets)); appendTo.remove(); - // Not having enough placeholder params results in error. - fmt.format( - params, - UPRV_LENGTHOF(params) - 1, - appendTo, - offsets, - UPRV_LENGTHOF(offsets), - status); - if (status != U_ILLEGAL_ARGUMENT_ERROR) { - errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); - } - // Ensure we don't write to offsets array beyond its length. status = U_ZERO_ERROR; offsets[UPRV_LENGTHOF(offsets) - 1] = 289; appendTo.remove(); - fmt.format( + fmt.formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -150,7 +157,7 @@ void SimplePatternFormatterTest::TestManyPlaceholders() { assertEquals( "Assignment", "Templates frogtommy{0} and leg are out of order.", - s.format( + s.formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -164,7 +171,7 @@ void SimplePatternFormatterTest::TestManyPlaceholders() { assertEquals( "Copy constructor", "Templates frogtommy{0} and leg are out of order.", - r.format( + r.formatAndAppend( params, UPRV_LENGTHOF(params), appendTo, @@ -195,42 +202,265 @@ void SimplePatternFormatterTest::TestManyPlaceholders() { assertSuccess("Status", status); } +void SimplePatternFormatterTest::TestTooFewPlaceholderValues() { + SimplePatternFormatter fmt("{0} and {1}"); + UnicodeString appendTo; + UnicodeString firstValue; + UnicodeString *params[] = {&firstValue}; + + UErrorCode status = U_ZERO_ERROR; + fmt.format( + firstValue, appendTo, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + + status = U_ZERO_ERROR; + fmt.formatAndAppend( + params, UPRV_LENGTHOF(params), appendTo, NULL, 0, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + + status = U_ZERO_ERROR; + fmt.formatAndReplace( + params, UPRV_LENGTHOF(params), appendTo, NULL, 0, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } +} + +void SimplePatternFormatterTest::TestBadArguments() { + SimplePatternFormatter fmt("pickle"); + UnicodeString appendTo; + UErrorCode status = U_ZERO_ERROR; + int32_t offsets[1]; + + // These succeed + fmt.formatAndAppend( + NULL, 0, appendTo, NULL, 0, status); + fmt.formatAndReplace( + NULL, 0, appendTo, NULL, 0, status); + assertSuccess("", status); + status = U_ZERO_ERROR; + + // fails + fmt.formatAndAppend( + NULL, 1, appendTo, NULL, 0, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + status = U_ZERO_ERROR; + + // fails + fmt.formatAndAppend( + NULL, 0, appendTo, NULL, 1, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + status = U_ZERO_ERROR; + + // fails because appendTo used as a parameter value + const UnicodeString *params[] = {&appendTo}; + fmt.formatAndAppend( + params, UPRV_LENGTHOF(params), appendTo, NULL, 0, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + status = U_ZERO_ERROR; + + + // fails + fmt.formatAndReplace( + NULL, 1, appendTo, NULL, 0, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } + status = U_ZERO_ERROR; + + // fails + fmt.formatAndReplace( + NULL, 0, appendTo, NULL, 1, status); + if (status != U_ILLEGAL_ARGUMENT_ERROR) { + errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); + } +} + void SimplePatternFormatterTest::TestGetPatternWithNoPlaceholders() { SimplePatternFormatter fmt("{0} has no {1} placeholders."); assertEquals( "", " has no placeholders.", fmt.getPatternWithNoPlaceholders()); } -void SimplePatternFormatterTest::TestOptimization() { +void SimplePatternFormatterTest::TestFormatReplaceNoOptimization() { UErrorCode status = U_ZERO_ERROR; SimplePatternFormatter fmt; fmt.compile("{2}, {0}, {1} and {3}", status); - assertSuccess("Status", status); - assertTrue("startsWithPlaceholder", fmt.startsWithPlaceholder(2)); - assertFalse("startsWithPlaceholder", fmt.startsWithPlaceholder(0)); - UnicodeString values[] = { - "freddy", "frog", "leg", "by"}; - UnicodeString *params[] = { - &values[0], &values[1], &values[2], &values[3]}; - int32_t offsets[4]; - int32_t expectedOffsets[4] = {5, 13, 0, 22}; - - // The pattern starts with {2}, so format should append the result of - // the rest of the pattern to values[2], the value for {2}. + if (!assertSuccess("Status", status)) { + return; + } + UnicodeString result("original"); + int offsets[4]; + UnicodeString freddy("freddy"); + UnicodeString frog("frog"); + UnicodeString by("by"); + const UnicodeString *params[] = {&result, &freddy, &frog, &by}; assertEquals( - "format", - "leg, freddy, frog and by", - fmt.format( + "", + "frog, original, freddy and by", + fmt.formatAndReplace( params, UPRV_LENGTHOF(params), - values[2], + result, offsets, UPRV_LENGTHOF(offsets), status)); + if (!assertSuccess("Status", status)) { + return; + } + int32_t expectedOffsets[] = {6, 16, 0, 27}; + verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets)); +} + +void SimplePatternFormatterTest::TestFormatReplaceNoOptimizationLeadingText() { + UErrorCode status = U_ZERO_ERROR; + SimplePatternFormatter fmt; + fmt.compile("boo {2}, {0}, {1} and {3}", status); + if (!assertSuccess("Status", status)) { + return; + } + UnicodeString result("original"); + int offsets[4]; + UnicodeString freddy("freddy"); + UnicodeString frog("frog"); + UnicodeString by("by"); + const UnicodeString *params[] = {&freddy, &frog, &result, &by}; + assertEquals( + "", + "boo original, freddy, frog and by", + fmt.formatAndReplace( + params, + UPRV_LENGTHOF(params), + result, + offsets, + UPRV_LENGTHOF(offsets), + status)); + if (!assertSuccess("Status", status)) { + return; + } + int32_t expectedOffsets[] = {14, 22, 4, 31}; + verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets)); +} + +void SimplePatternFormatterTest::TestFormatReplaceOptimization() { + UErrorCode status = U_ZERO_ERROR; + SimplePatternFormatter fmt; + fmt.compile("{2}, {0}, {1} and {3}", status); + if (!assertSuccess("Status", status)) { + return; + } + UnicodeString result("original"); + int offsets[4]; + UnicodeString freddy("freddy"); + UnicodeString frog("frog"); + UnicodeString by("by"); + const UnicodeString *params[] = {&freddy, &frog, &result, &by}; + assertEquals( + "", + "original, freddy, frog and by", + fmt.formatAndReplace( + params, + UPRV_LENGTHOF(params), + result, + offsets, + UPRV_LENGTHOF(offsets), + status)); + if (!assertSuccess("Status", status)) { + return; + } + int32_t expectedOffsets[] = {10, 18, 0, 27}; + verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets)); +} + +void SimplePatternFormatterTest::TestFormatReplaceNoOptimizationLeadingPlaceholderUsedTwice() { + UErrorCode status = U_ZERO_ERROR; + SimplePatternFormatter fmt; + fmt.compile("{2}, {0}, {1} and {3} {2}", status); + if (!assertSuccess("Status", status)) { + return; + } + UnicodeString result("original"); + int offsets[4]; + UnicodeString freddy("freddy"); + UnicodeString frog("frog"); + UnicodeString by("by"); + const UnicodeString *params[] = {&freddy, &frog, &result, &by}; + assertEquals( + "", + "original, freddy, frog and by original", + fmt.formatAndReplace( + params, + UPRV_LENGTHOF(params), + result, + offsets, + UPRV_LENGTHOF(offsets), + status)); + if (!assertSuccess("Status", status)) { + return; + } + int32_t expectedOffsets[] = {10, 18, 30, 27}; + verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets)); +} + +void SimplePatternFormatterTest::TestFormatReplaceOptimizationNoOffsets() { + UErrorCode status = U_ZERO_ERROR; + SimplePatternFormatter fmt; + fmt.compile("{2}, {0}, {1} and {3}", status); + if (!assertSuccess("Status", status)) { + return; + } + UnicodeString result("original"); + UnicodeString freddy("freddy"); + UnicodeString frog("frog"); + UnicodeString by("by"); + const UnicodeString *params[] = {&freddy, &frog, &result, &by}; + assertEquals( + "", + "original, freddy, frog and by", + fmt.formatAndReplace( + params, + UPRV_LENGTHOF(params), + result, + NULL, + 0, + status)); assertSuccess("Status", status); - for (int32_t i = 0; i < UPRV_LENGTHOF(expectedOffsets); ++i) { - if (expectedOffsets[i] != offsets[i]) { - errln("Expected %d, got %d", expectedOffsets[i], offsets[i]); +} + +void SimplePatternFormatterTest::TestFormatReplaceNoOptimizationNoOffsets() { + UErrorCode status = U_ZERO_ERROR; + SimplePatternFormatter fmt("Placeholders {0} and {1}"); + UnicodeString result("previous:"); + UnicodeString frog("frog"); + const UnicodeString *params[] = {&result, &frog}; + assertEquals( + "", + "Placeholders previous: and frog", + fmt.formatAndReplace( + params, + UPRV_LENGTHOF(params), + result, + NULL, + 0, + status)); + assertSuccess("Status", status); +} + +void SimplePatternFormatterTest::verifyOffsets( + const int32_t *expected, const int32_t *actual, int32_t count) { + for (int32_t i = 0; i < count; ++i) { + if (expected[i] != actual[i]) { + errln("Expected %d, got %d", expected[i], actual[i]); } } } @@ -238,3 +468,4 @@ void SimplePatternFormatterTest::TestOptimization() { extern IntlTest *createSimplePatternFormatterTest() { return new SimplePatternFormatterTest(); } +