ICU-12863 Add list style APIs to C and C++

See #894
This commit is contained in:
Shane Carr 2019-11-16 07:28:26 +00:00 committed by Shane F. Carr
parent faa2f9f9e1
commit 46ec4fd523
7 changed files with 304 additions and 54 deletions

View file

@ -80,7 +80,6 @@ UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedList)
static Hashtable* listPatternHash = nullptr;
static const char STANDARD_STYLE[] = "standard";
U_CDECL_BEGIN
static UBool U_CALLCONV uprv_listformatter_cleanup() {
@ -179,6 +178,50 @@ const ListFormatInternal* ListFormatter::getListFormatInternal(
return result;
}
static const char* typeWidthToStyleString(UListFormatterType type, UListFormatterWidth width) {
switch (type) {
case ULISTFMT_TYPE_AND:
switch (width) {
case ULISTFMT_WIDTH_WIDE:
return "standard";
case ULISTFMT_WIDTH_SHORT:
return "standard-short";
case ULISTFMT_WIDTH_NARROW:
return "standard-narrow";
default:
return nullptr;
}
break;
case ULISTFMT_TYPE_OR:
switch (width) {
case ULISTFMT_WIDTH_WIDE:
return "or";
case ULISTFMT_WIDTH_SHORT:
return "or-short";
case ULISTFMT_WIDTH_NARROW:
return "or-narrow";
default:
return nullptr;
}
break;
case ULISTFMT_TYPE_UNITS:
switch (width) {
case ULISTFMT_WIDTH_WIDE:
return "unit";
case ULISTFMT_WIDTH_SHORT:
return "unit-short";
case ULISTFMT_WIDTH_NARROW:
return "unit-narrow";
default:
return nullptr;
}
}
return nullptr;
}
static const UChar solidus = 0x2F;
static const UChar aliasPrefix[] = { 0x6C,0x69,0x73,0x74,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x2F }; // "listPattern/"
enum {
@ -297,7 +340,17 @@ ListFormatter* ListFormatter::createInstance(UErrorCode& errorCode) {
}
ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
return createInstance(locale, STANDARD_STYLE, errorCode);
return createInstance(locale, ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_WIDE, errorCode);
}
ListFormatter* ListFormatter::createInstance(
const Locale& locale, UListFormatterType type, UListFormatterWidth width, UErrorCode& errorCode) {
const char* style = typeWidthToStyleString(type, width);
if (style == nullptr) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
return createInstance(locale, style, errorCode);
}
ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {

View file

@ -34,6 +34,21 @@ ulistfmt_open(const char* locale,
}
U_CAPI UListFormatter* U_EXPORT2
ulistfmt_openForType(const char* locale, UListFormatterType type,
UListFormatterWidth width, UErrorCode* status)
{
if (U_FAILURE(*status)) {
return NULL;
}
LocalPointer<ListFormatter> listfmt(ListFormatter::createInstance(Locale(locale), type, width, *status));
if (U_FAILURE(*status)) {
return NULL;
}
return (UListFormatter*)listfmt.orphan();
}
U_CAPI void U_EXPORT2
ulistfmt_close(UListFormatter *listfmt)
{

View file

@ -26,6 +26,7 @@
#include "unicode/unistr.h"
#include "unicode/locid.h"
#include "unicode/formattedvalue.h"
#include "unicode/ulistformatter.h"
U_NAMESPACE_BEGIN
@ -185,10 +186,27 @@ class U_I18N_API ListFormatter : public UObject{
*/
static ListFormatter* createInstance(const Locale& locale, UErrorCode& errorCode);
#ifndef U_HIDE_DRAFT_API
/**
* Creates a ListFormatter for the given locale, list type, and style.
*
* @param locale The locale.
* @param type The type of list formatting to use.
* @param width The width of formatting to use.
* @param errorCode ICU error code, set if no data available for the given locale.
* @return A ListFormatter object created from internal data derived from CLDR data.
* @draft ICU 67
*/
static ListFormatter* createInstance(
const Locale& locale, UListFormatterType type, UListFormatterWidth width, UErrorCode& errorCode);
#endif /* U_HIDE_DRAFT_API */
#ifndef U_HIDE_INTERNAL_API
/**
* Creates a ListFormatter appropriate for a locale and style.
*
* TODO(ICU-20888): Remove this in ICU 68.
*
* @param locale The locale.
* @param style the style, either "standard", "or", "unit", "unit-narrow", or "unit-short"
* @param errorCode ICU error code, set if no data available for the given locale.

View file

@ -61,10 +61,67 @@ typedef enum UListFormatterField {
*/
ULISTFMT_ELEMENT_FIELD
} UListFormatterField;
/**
* Type of meaning expressed by the list.
*
* @draft ICU 67
*/
typedef enum UListFormatterType {
/**
* Conjunction formatting, e.g. "Alice, Bob, Charlie, and Delta".
*
* @draft ICU 67
*/
ULISTFMT_TYPE_AND,
/**
* Disjunction (or alternative, or simply one of) formatting, e.g.
* "Alice, Bob, Charlie, or Delta".
*
* @draft ICU 67
*/
ULISTFMT_TYPE_OR,
/**
* Formatting of a list of values with units, e.g. "5 pounds, 12 ounces".
*
* @draft ICU 67
*/
ULISTFMT_TYPE_UNITS
} UListFormatterType;
/**
* Verbosity level of the list patterns.
*
* @draft ICU 67
*/
typedef enum UListFormatterWidth {
/**
* Use list formatting with full words (no abbreviations) when possible.
*
* @draft ICU 67
*/
ULISTFMT_WIDTH_WIDE,
/**
* Use list formatting of typical length.
* @draft ICU 67
*/
ULISTFMT_WIDTH_SHORT,
/**
* Use list formatting of the shortest possible length.
* @draft ICU 67
*/
ULISTFMT_WIDTH_NARROW,
} UListFormatterWidth;
#endif /* U_HIDE_DRAFT_API */
/**
* Open a new UListFormatter object using the rules for a given locale.
* The object will be initialized with AND type and WIDE width.
*
* @param locale
* The locale whose rules should be used; may be NULL for
* default locale.
@ -83,6 +140,34 @@ U_CAPI UListFormatter* U_EXPORT2
ulistfmt_open(const char* locale,
UErrorCode* status);
#ifndef U_HIDE_DRAFT_API
/**
* Open a new UListFormatter object appropriate for the given locale, list type,
* and style.
*
* @param locale
* The locale whose rules should be used; may be NULL for
* default locale.
* @param type
* The type of list formatting to use.
* @param width
* The width of formatting to use.
* @param status
* A pointer to a standard ICU UErrorCode (input/output parameter).
* Its input value must pass the U_SUCCESS() test, or else the
* function returns immediately. The caller should check its output
* value with U_FAILURE(), or use with function chaining (see User
* Guide for details).
* @return
* A pointer to a UListFormatter object for the specified locale,
* or NULL if an error occurred.
* @draft ICU 67
*/
U_CAPI UListFormatter* U_EXPORT2
ulistfmt_openForType(const char* locale, UListFormatterType type,
UListFormatterWidth width, UErrorCode* status);
#endif /* U_HIDE_DRAFT_API */
/**
* Close a UListFormatter object. Once closed it may no longer be used.
* @param listfmt

View file

@ -19,6 +19,7 @@
static void TestUListFmt(void);
static void TestUListFmtToValue(void);
static void TestUListOpenStyled(void);
void addUListFmtTest(TestNode** root);
@ -28,6 +29,7 @@ void addUListFmtTest(TestNode** root)
{
TESTCASE(TestUListFmt);
TESTCASE(TestUListFmtToValue);
TESTCASE(TestUListOpenStyled);
}
static const UChar str0[] = { 0x41,0 }; /* "A" */
@ -210,5 +212,43 @@ static void TestUListFmtToValue() {
ulistfmt_closeResult(fl);
}
static void TestUListOpenStyled() {
UErrorCode ec = U_ZERO_ERROR;
UListFormatter* fmt = ulistfmt_openForType("en", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_SHORT, &ec);
UFormattedList* fl = ulistfmt_openResult(&ec);
assertSuccess("Opening", &ec);
{
const char* message = "openStyled test 1";
const UChar* expectedString = u"A, B, or C";
const UChar* inputs[] = {
u"A",
u"B",
u"C",
};
ulistfmt_formatStringsToResult(fmt, inputs, NULL, UPRV_LENGTHOF(inputs), fl, &ec);
assertSuccess("Formatting", &ec);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// field, begin index, end index
{UFIELD_CATEGORY_LIST_SPAN, 0, 0, 1},
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD, 0, 1},
{UFIELD_CATEGORY_LIST, ULISTFMT_LITERAL_FIELD, 1, 3},
{UFIELD_CATEGORY_LIST_SPAN, 1, 3, 4},
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD, 3, 4},
{UFIELD_CATEGORY_LIST, ULISTFMT_LITERAL_FIELD, 4, 9},
{UFIELD_CATEGORY_LIST_SPAN, 2, 9, 10},
{UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD, 9, 10}};
checkMixedFormattedValue(
message,
ulistfmt_resultAsValue(fl, &ec),
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
ulistfmt_close(fmt);
ulistfmt_closeResult(fl);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -23,6 +23,37 @@
#if !UCONFIG_NO_FORMATTING
void ListFormatterTest::runIndexedTest(int32_t index, UBool exec,
const char* &name, char* /*par */) {
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(TestRoot);
TESTCASE_AUTO(TestBogus);
TESTCASE_AUTO(TestEnglish);
TESTCASE_AUTO(TestEnglishUS);
TESTCASE_AUTO(TestRussian);
TESTCASE_AUTO(TestMalayalam);
TESTCASE_AUTO(TestZulu);
TESTCASE_AUTO(TestOutOfOrderPatterns);
TESTCASE_AUTO(Test9946);
TESTCASE_AUTO(TestEnglishGB);
TESTCASE_AUTO(TestNynorsk);
TESTCASE_AUTO(TestChineseTradHK);
TESTCASE_AUTO(TestFieldPositionIteratorWontCrash);
TESTCASE_AUTO(TestFieldPositionIteratorWith1Item);
TESTCASE_AUTO(TestFieldPositionIteratorWith1ItemAndDataBefore);
TESTCASE_AUTO(TestFieldPositionIteratorWith2Items);
TESTCASE_AUTO(TestFieldPositionIteratorWith2ItemsAndDataBefore);
TESTCASE_AUTO(TestFieldPositionIteratorWith2ItemsPatternShift);
TESTCASE_AUTO(TestFieldPositionIteratorWith3Items);
TESTCASE_AUTO(TestFieldPositionIteratorWith3ItemsAndDataBefore);
TESTCASE_AUTO(TestFieldPositionIteratorWith3ItemsPatternShift);
TESTCASE_AUTO(TestFormattedValue);
TESTCASE_AUTO(TestDifferentStyles);
TESTCASE_AUTO(TestBadStylesFail);
TESTCASE_AUTO(TestCreateStyled);
TESTCASE_AUTO_END;
}
namespace {
const char* attrString(int32_t attrId) {
switch (attrId) {
@ -611,58 +642,65 @@ void ListFormatterTest::TestBadStylesFail() {
}
}
void ListFormatterTest::runIndexedTest(int32_t index, UBool exec,
const char* &name, char* /*par */) {
switch(index) {
case 0: name = "TestRoot"; if (exec) TestRoot(); break;
case 1: name = "TestBogus"; if (exec) TestBogus(); break;
case 2: name = "TestEnglish"; if (exec) TestEnglish(); break;
case 3: name = "TestEnglishUS"; if (exec) TestEnglishUS(); break;
case 4: name = "TestRussian"; if (exec) TestRussian(); break;
case 5: name = "TestMalayalam"; if (exec) TestMalayalam(); break;
case 6: name = "TestZulu"; if (exec) TestZulu(); break;
case 7: name = "TestOutOfOrderPatterns"; if (exec) TestOutOfOrderPatterns(); break;
case 8: name = "Test9946"; if (exec) Test9946(); break;
case 9: name = "TestEnglishGB"; if (exec) TestEnglishGB(); break;
case 10: name = "TestNynorsk"; if (exec) TestNynorsk(); break;
case 11: name = "TestChineseTradHK"; if (exec) TestChineseTradHK(); break;
case 12: name = "TestFieldPositionIteratorWontCrash";
if (exec) TestFieldPositionIteratorWontCrash();
break;
case 13: name = "TestFieldPositionIteratorWith1Item";
if (exec) TestFieldPositionIteratorWith1Item();
break;
case 14: name = "TestFieldPositionIteratorWith1ItemAndDataBefore";
if (exec) TestFieldPositionIteratorWith1ItemAndDataBefore();
break;
case 15: name = "TestFieldPositionIteratorWith2Items";
if (exec) TestFieldPositionIteratorWith2Items();
break;
case 16: name = "TestFieldPositionIteratorWith2ItemsAndDataBefore";
if (exec) TestFieldPositionIteratorWith2ItemsAndDataBefore();
break;
case 17: name = "TestFieldPositionIteratorWith2ItemsPatternShift";
if (exec) TestFieldPositionIteratorWith2ItemsPatternShift();
break;
case 18: name = "TestFieldPositionIteratorWith3Items";
if (exec) TestFieldPositionIteratorWith3Items();
break;
case 19: name = "TestFieldPositionIteratorWith3ItemsAndDataBefore";
if (exec) TestFieldPositionIteratorWith3ItemsAndDataBefore();
break;
case 20: name = "TestFieldPositionIteratorWith3ItemsPatternShift";
if (exec) TestFieldPositionIteratorWith3ItemsPatternShift();
break;
case 21: name = "TestFormattedValue";
if (exec) TestFormattedValue();
break;
case 22: name = "TestDifferentStyles";
if (exec) TestDifferentStyles();
break;
case 23: name = "TestBadStylesFail";
if (exec) TestBadStylesFail();
break;
default: name = ""; break;
void ListFormatterTest::TestCreateStyled() {
IcuTestErrorCode status(*this, "TestCreateStyled");
// Locale en has interesting data
struct TestCase {
const char* locale;
UListFormatterType type;
UListFormatterWidth width;
const char16_t* expected3;
const char16_t* expected2;
const char16_t* expected1;
} cases[] = {
{ "pt", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_WIDE, u"A, B e C", u"A e B", u"A" },
{ "pt", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_SHORT, u"A, B e C", u"A e B", u"A" },
{ "pt", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_NARROW, u"A, B, C", u"A, B", u"A" },
{ "pt", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_WIDE, u"A, B ou C", u"A ou B", u"A" },
{ "pt", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_SHORT, u"A, B ou C", u"A ou B", u"A" },
{ "pt", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_NARROW, u"A, B ou C", u"A ou B", u"A" },
{ "pt", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_WIDE, u"A, B e C", u"A e B", u"A" },
{ "pt", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_SHORT, u"A, B e C", u"A e B", u"A" },
{ "pt", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_NARROW, u"A B C", u"A B", u"A" },
{ "en", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_WIDE, u"A, B, and C", u"A and B", u"A" },
{ "en", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_SHORT, u"A, B, & C", u"A & B", u"A" },
{ "en", ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_NARROW, u"A, B, C", u"A, B", u"A" },
{ "en", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_WIDE, u"A, B, or C", u"A or B", u"A" },
{ "en", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_SHORT, u"A, B, or C", u"A or B", u"A" },
{ "en", ULISTFMT_TYPE_OR, ULISTFMT_WIDTH_NARROW, u"A, B, or C", u"A or B", u"A" },
{ "en", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_WIDE, u"A, B, C", u"A, B", u"A" },
{ "en", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_SHORT, u"A, B, C", u"A, B", u"A" },
{ "en", ULISTFMT_TYPE_UNITS, ULISTFMT_WIDTH_NARROW, u"A B C", u"A B", u"A" },
};
for (auto cas : cases) {
LocalPointer<ListFormatter> fmt(
ListFormatter::createInstance(cas.locale, cas.type, cas.width, status),
status);
if (status.errIfFailureAndReset()) {
continue;
}
UnicodeString message = UnicodeString(u"TestCreateStyled loc=")
+ cas.locale + u" type="
+ Int64ToUnicodeString(cas.type) + u" width="
+ Int64ToUnicodeString(cas.width);
const UnicodeString inputs3[] = {
u"A",
u"B",
u"C"
};
FormattedList result = fmt->formatStringsToValue(inputs3, UPRV_LENGTHOF(inputs3), status);
assertEquals(message, cas.expected3, result.toTempString(status));
const UnicodeString inputs2[] = {
u"A",
u"B"
};
result = fmt->formatStringsToValue(inputs2, UPRV_LENGTHOF(inputs2), status);
assertEquals(message, cas.expected2, result.toTempString(status));
const UnicodeString inputs1[] = {
u"A"
};
result = fmt->formatStringsToValue(inputs1, UPRV_LENGTHOF(inputs1), status);
assertEquals(message, cas.expected1, result.toTempString(status));
}
}

View file

@ -57,6 +57,7 @@ class ListFormatterTest : public IntlTestWithFieldPosition {
void TestFormattedValue();
void TestDifferentStyles();
void TestBadStylesFail();
void TestCreateStyled();
private:
void CheckFormatting(