ICU-20119 63rc BRS, merge current unicode-icu master into cldr34a-integration branch
This commit is contained in:
commit
f5defe757b
@ -126,15 +126,21 @@ char *CharString::getAppendBuffer(int32_t minCapacity,
|
||||
}
|
||||
|
||||
CharString &CharString::appendInvariantChars(const UnicodeString &s, UErrorCode &errorCode) {
|
||||
return appendInvariantChars(s.getBuffer(), s.length(), errorCode);
|
||||
}
|
||||
|
||||
CharString &CharString::appendInvariantChars(const UChar* uchars, int32_t ucharsLen, UErrorCode &errorCode) {
|
||||
if(U_FAILURE(errorCode)) {
|
||||
return *this;
|
||||
}
|
||||
if (!uprv_isInvariantUnicodeString(s)) {
|
||||
if (!uprv_isInvariantUString(uchars, ucharsLen)) {
|
||||
errorCode = U_INVARIANT_CONVERSION_ERROR;
|
||||
return *this;
|
||||
}
|
||||
if(ensureCapacity(len+s.length()+1, 0, errorCode)) {
|
||||
len+=s.extract(0, 0x7fffffff, buffer.getAlias()+len, buffer.getCapacity()-len, US_INV);
|
||||
if(ensureCapacity(len+ucharsLen+1, 0, errorCode)) {
|
||||
u_UCharsToChars(uchars, buffer.getAlias()+len, ucharsLen);
|
||||
len += ucharsLen;
|
||||
buffer[len] = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ public:
|
||||
UErrorCode &errorCode);
|
||||
|
||||
CharString &appendInvariantChars(const UnicodeString &s, UErrorCode &errorCode);
|
||||
CharString &appendInvariantChars(const UChar* uchars, int32_t ucharsLen, UErrorCode& errorCode);
|
||||
|
||||
/**
|
||||
* Appends a filename/path part, e.g., a directory name.
|
||||
|
@ -279,6 +279,10 @@ inline T *LocalMemory<T>::allocateInsteadAndCopy(int32_t newCapacity, int32_t le
|
||||
*
|
||||
* Unlike LocalMemory and LocalArray, this class never adopts
|
||||
* (takes ownership of) another array.
|
||||
*
|
||||
* WARNING: MaybeStackArray only works with primitive (plain-old data) types.
|
||||
* It does NOT know how to call a destructor! If you work with classes with
|
||||
* destructors, consider LocalArray in localpointer.h.
|
||||
*/
|
||||
template<typename T, int32_t stackCapacity>
|
||||
class MaybeStackArray {
|
||||
|
@ -31,6 +31,7 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "unicode/bytestream.h"
|
||||
#include "unicode/locid.h"
|
||||
@ -45,9 +46,11 @@
|
||||
#include "cstring.h"
|
||||
#include "uassert.h"
|
||||
#include "uhash.h"
|
||||
#include "ulocimp.h"
|
||||
#include "ucln_cmn.h"
|
||||
#include "ustr_imp.h"
|
||||
#include "charstr.h"
|
||||
#include "bytesinkutil.h"
|
||||
|
||||
U_CDECL_BEGIN
|
||||
static UBool U_CALLCONV locale_cleanup(void);
|
||||
@ -426,56 +429,70 @@ Locale::Locale(const Locale &other)
|
||||
*this = other;
|
||||
}
|
||||
|
||||
Locale &Locale::operator=(const Locale &other)
|
||||
{
|
||||
Locale::Locale(Locale&& other) U_NOEXCEPT
|
||||
: UObject(other), fullName(fullNameBuffer), baseName(fullName) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
Locale& Locale::operator=(const Locale& other) {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Free our current storage */
|
||||
if (baseName != fullName) {
|
||||
uprv_free(baseName);
|
||||
}
|
||||
baseName = NULL;
|
||||
if(fullName != fullNameBuffer) {
|
||||
uprv_free(fullName);
|
||||
fullName = fullNameBuffer;
|
||||
setToBogus();
|
||||
|
||||
if (other.fullName == other.fullNameBuffer) {
|
||||
uprv_strcpy(fullNameBuffer, other.fullNameBuffer);
|
||||
} else if (other.fullName == nullptr) {
|
||||
fullName = nullptr;
|
||||
} else {
|
||||
fullName = uprv_strdup(other.fullName);
|
||||
if (fullName == nullptr) return *this;
|
||||
}
|
||||
|
||||
/* Allocate the full name if necessary */
|
||||
if(other.fullName != other.fullNameBuffer) {
|
||||
fullName = (char *)uprv_malloc(sizeof(char)*(uprv_strlen(other.fullName)+1));
|
||||
if (fullName == NULL) {
|
||||
// if memory allocation fails, set this object to bogus.
|
||||
fIsBogus = TRUE;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
/* Copy the full name */
|
||||
uprv_strcpy(fullName, other.fullName);
|
||||
|
||||
/* Copy the baseName if it differs from fullName. */
|
||||
if (other.baseName == other.fullName) {
|
||||
baseName = fullName;
|
||||
} else {
|
||||
if (other.baseName) {
|
||||
} else if (other.baseName != nullptr) {
|
||||
baseName = uprv_strdup(other.baseName);
|
||||
if (baseName == nullptr) {
|
||||
// if memory allocation fails, set this object to bogus.
|
||||
fIsBogus = TRUE;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
if (baseName == nullptr) return *this;
|
||||
}
|
||||
|
||||
/* Copy the language and country fields */
|
||||
uprv_strcpy(language, other.language);
|
||||
uprv_strcpy(script, other.script);
|
||||
uprv_strcpy(country, other.country);
|
||||
|
||||
/* The variantBegin is an offset, just copy it */
|
||||
variantBegin = other.variantBegin;
|
||||
fIsBogus = other.fIsBogus;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Locale& Locale::operator=(Locale&& other) U_NOEXCEPT {
|
||||
if (baseName != fullName) uprv_free(baseName);
|
||||
if (fullName != fullNameBuffer) uprv_free(fullName);
|
||||
|
||||
if (other.fullName == other.fullNameBuffer) {
|
||||
uprv_strcpy(fullNameBuffer, other.fullNameBuffer);
|
||||
fullName = fullNameBuffer;
|
||||
} else {
|
||||
fullName = other.fullName;
|
||||
}
|
||||
|
||||
if (other.baseName == other.fullName) {
|
||||
baseName = fullName;
|
||||
} else {
|
||||
baseName = other.baseName;
|
||||
}
|
||||
|
||||
uprv_strcpy(language, other.language);
|
||||
uprv_strcpy(script, other.script);
|
||||
uprv_strcpy(country, other.country);
|
||||
|
||||
variantBegin = other.variantBegin;
|
||||
fIsBogus = other.fIsBogus;
|
||||
|
||||
other.baseName = other.fullName = other.fullNameBuffer;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -713,6 +730,126 @@ Locale::setDefault( const Locale& newLocale,
|
||||
locale_set_default_internal(localeID, status);
|
||||
}
|
||||
|
||||
void
|
||||
Locale::addLikelySubtags(UErrorCode& status) {
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The maximized locale ID string is often longer, but there is no good
|
||||
// heuristic to estimate just how much longer. Leave that to CharString.
|
||||
CharString maximizedLocaleID;
|
||||
int32_t maximizedLocaleIDCapacity = uprv_strlen(fullName);
|
||||
|
||||
char* buffer;
|
||||
int32_t reslen;
|
||||
|
||||
for (;;) {
|
||||
buffer = maximizedLocaleID.getAppendBuffer(
|
||||
/*minCapacity=*/maximizedLocaleIDCapacity,
|
||||
/*desiredCapacityHint=*/maximizedLocaleIDCapacity,
|
||||
maximizedLocaleIDCapacity,
|
||||
status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reslen = uloc_addLikelySubtags(
|
||||
fullName,
|
||||
buffer,
|
||||
maximizedLocaleIDCapacity,
|
||||
&status);
|
||||
|
||||
if (status != U_BUFFER_OVERFLOW_ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
maximizedLocaleIDCapacity = reslen;
|
||||
status = U_ZERO_ERROR;
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
maximizedLocaleID.append(buffer, reslen, status);
|
||||
if (status == U_STRING_NOT_TERMINATED_WARNING) {
|
||||
status = U_ZERO_ERROR; // Terminators provided by CharString.
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
init(maximizedLocaleID.data(), /*canonicalize=*/FALSE);
|
||||
if (isBogus()) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Locale::minimizeSubtags(UErrorCode& status) {
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Except for a few edge cases (like the empty string, that is minimized to
|
||||
// "en__POSIX"), minimized locale ID strings will be either the same length
|
||||
// or shorter than their input.
|
||||
CharString minimizedLocaleID;
|
||||
int32_t minimizedLocaleIDCapacity = uprv_strlen(fullName);
|
||||
|
||||
char* buffer;
|
||||
int32_t reslen;
|
||||
|
||||
for (;;) {
|
||||
buffer = minimizedLocaleID.getAppendBuffer(
|
||||
/*minCapacity=*/minimizedLocaleIDCapacity,
|
||||
/*desiredCapacityHint=*/minimizedLocaleIDCapacity,
|
||||
minimizedLocaleIDCapacity,
|
||||
status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reslen = uloc_minimizeSubtags(
|
||||
fullName,
|
||||
buffer,
|
||||
minimizedLocaleIDCapacity,
|
||||
&status);
|
||||
|
||||
if (status != U_BUFFER_OVERFLOW_ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Because of the internal minimal buffer size of CharString, I can't
|
||||
// think of any input data for which this could possibly ever happen.
|
||||
// Maybe it would be better replaced with an assertion instead?
|
||||
minimizedLocaleIDCapacity = reslen;
|
||||
status = U_ZERO_ERROR;
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
minimizedLocaleID.append(buffer, reslen, status);
|
||||
if (status == U_STRING_NOT_TERMINATED_WARNING) {
|
||||
status = U_ZERO_ERROR; // Terminators provided by CharString.
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
init(minimizedLocaleID.data(), /*canonicalize=*/FALSE);
|
||||
if (isBogus()) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
Locale U_EXPORT2
|
||||
Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
|
||||
{
|
||||
@ -722,12 +859,6 @@ Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Remove the need for a const char* to a NUL terminated buffer.
|
||||
const CharString tag_nul(tag, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If a BCP-47 language tag is passed as the language parameter to the
|
||||
// normal Locale constructor, it will actually fall back to invoking
|
||||
// uloc_forLanguageTag() to parse it if it somehow is able to detect that
|
||||
@ -757,8 +888,9 @@ Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
|
||||
return result;
|
||||
}
|
||||
|
||||
reslen = uloc_forLanguageTag(
|
||||
tag_nul.data(),
|
||||
reslen = ulocimp_forLanguageTag(
|
||||
tag.data(),
|
||||
tag.length(),
|
||||
buffer,
|
||||
resultCapacity,
|
||||
&parsedLength,
|
||||
@ -1174,20 +1306,79 @@ KeywordEnumeration::~KeywordEnumeration() {
|
||||
uprv_free(keywords);
|
||||
}
|
||||
|
||||
// A wrapper around KeywordEnumeration that calls uloc_toUnicodeLocaleKey() in
|
||||
// the next() method for each keyword before returning it.
|
||||
class UnicodeKeywordEnumeration : public KeywordEnumeration {
|
||||
public:
|
||||
using KeywordEnumeration::KeywordEnumeration;
|
||||
virtual ~UnicodeKeywordEnumeration() = default;
|
||||
|
||||
virtual const char* next(int32_t* resultLength, UErrorCode& status) {
|
||||
const char* legacy_key = KeywordEnumeration::next(nullptr, status);
|
||||
if (U_SUCCESS(status) && legacy_key != nullptr) {
|
||||
const char* key = uloc_toUnicodeLocaleKey(legacy_key);
|
||||
if (key == nullptr) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
} else {
|
||||
if (resultLength != nullptr) *resultLength = uprv_strlen(key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
if (resultLength != nullptr) *resultLength = 0;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
StringEnumeration *
|
||||
Locale::createKeywords(UErrorCode &status) const
|
||||
{
|
||||
char keywords[256];
|
||||
int32_t keywordCapacity = 256;
|
||||
int32_t keywordCapacity = sizeof keywords;
|
||||
StringEnumeration *result = NULL;
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* variantStart = uprv_strchr(fullName, '@');
|
||||
const char* assignment = uprv_strchr(fullName, '=');
|
||||
if(variantStart) {
|
||||
if(assignment > variantStart) {
|
||||
int32_t keyLen = locale_getKeywords(variantStart+1, '@', keywords, keywordCapacity, NULL, 0, NULL, FALSE, &status);
|
||||
if(keyLen) {
|
||||
if(U_SUCCESS(status) && keyLen) {
|
||||
result = new KeywordEnumeration(keywords, keyLen, 0, status);
|
||||
if (!result) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StringEnumeration *
|
||||
Locale::createUnicodeKeywords(UErrorCode &status) const
|
||||
{
|
||||
char keywords[256];
|
||||
int32_t keywordCapacity = sizeof keywords;
|
||||
StringEnumeration *result = NULL;
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* variantStart = uprv_strchr(fullName, '@');
|
||||
const char* assignment = uprv_strchr(fullName, '=');
|
||||
if(variantStart) {
|
||||
if(assignment > variantStart) {
|
||||
int32_t keyLen = locale_getKeywords(variantStart+1, '@', keywords, keywordCapacity, NULL, 0, NULL, FALSE, &status);
|
||||
if(U_SUCCESS(status) && keyLen) {
|
||||
result = new UnicodeKeywordEnumeration(keywords, keyLen, 0, status);
|
||||
if (!result) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
@ -1202,6 +1393,105 @@ Locale::getKeywordValue(const char* keywordName, char *buffer, int32_t bufLen, U
|
||||
return uloc_getKeywordValue(fullName, keywordName, buffer, bufLen, &status);
|
||||
}
|
||||
|
||||
void
|
||||
Locale::getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fIsBogus) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove the need for a const char* to a NUL terminated buffer.
|
||||
const CharString keywordName_nul(keywordName, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalMemory<char> scratch;
|
||||
int32_t scratch_capacity = 16; // Arbitrarily chosen default size.
|
||||
|
||||
char* buffer;
|
||||
int32_t result_capacity, reslen;
|
||||
|
||||
for (;;) {
|
||||
if (scratch.allocateInsteadAndReset(scratch_capacity) == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = sink.GetAppendBuffer(
|
||||
/*min_capacity=*/scratch_capacity,
|
||||
/*desired_capacity_hint=*/scratch_capacity,
|
||||
scratch.getAlias(),
|
||||
scratch_capacity,
|
||||
&result_capacity);
|
||||
|
||||
reslen = uloc_getKeywordValue(
|
||||
fullName,
|
||||
keywordName_nul.data(),
|
||||
buffer,
|
||||
result_capacity,
|
||||
&status);
|
||||
|
||||
if (status != U_BUFFER_OVERFLOW_ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
scratch_capacity = reslen;
|
||||
status = U_ZERO_ERROR;
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sink.Append(buffer, reslen);
|
||||
if (status == U_STRING_NOT_TERMINATED_WARNING) {
|
||||
status = U_ZERO_ERROR; // Terminators not used.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Locale::getUnicodeKeywordValue(StringPiece keywordName,
|
||||
ByteSink& sink,
|
||||
UErrorCode& status) const {
|
||||
// TODO: Remove the need for a const char* to a NUL terminated buffer.
|
||||
const CharString keywordName_nul(keywordName, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data());
|
||||
|
||||
if (legacy_key == nullptr) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
CharString legacy_value;
|
||||
{
|
||||
CharStringByteSink sink(&legacy_value);
|
||||
getKeywordValue(legacy_key, sink, status);
|
||||
}
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* unicode_value = uloc_toUnicodeLocaleType(
|
||||
keywordName_nul.data(), legacy_value.data());
|
||||
|
||||
if (unicode_value == nullptr) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
sink.Append(unicode_value, uprv_strlen(unicode_value));
|
||||
}
|
||||
|
||||
void
|
||||
Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status)
|
||||
{
|
||||
@ -1212,6 +1502,46 @@ Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErro
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Locale::setKeywordValue(StringPiece keywordName,
|
||||
StringPiece keywordValue,
|
||||
UErrorCode& status) {
|
||||
// TODO: Remove the need for a const char* to a NUL terminated buffer.
|
||||
const CharString keywordName_nul(keywordName, status);
|
||||
const CharString keywordValue_nul(keywordValue, status);
|
||||
setKeywordValue(keywordName_nul.data(), keywordValue_nul.data(), status);
|
||||
}
|
||||
|
||||
void
|
||||
Locale::setUnicodeKeywordValue(StringPiece keywordName,
|
||||
StringPiece keywordValue,
|
||||
UErrorCode& status) {
|
||||
// TODO: Remove the need for a const char* to a NUL terminated buffer.
|
||||
const CharString keywordName_nul(keywordName, status);
|
||||
const CharString keywordValue_nul(keywordValue, status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data());
|
||||
|
||||
if (legacy_key == nullptr) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
const char* legacy_value =
|
||||
uloc_toLegacyType(keywordName_nul.data(), keywordValue_nul.data());
|
||||
|
||||
if (legacy_value == nullptr) {
|
||||
status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
setKeywordValue(legacy_key, legacy_value, status);
|
||||
}
|
||||
|
||||
const char *
|
||||
Locale::getBaseName() const {
|
||||
return baseName;
|
||||
|
@ -1668,7 +1668,8 @@ The leftmost codepage (.xxx) wins.
|
||||
/* Note that we scan the *uncorrected* ID. */
|
||||
if ((p = uprv_strrchr(posixID, '@')) != NULL) {
|
||||
if (correctedPOSIXLocale == NULL) {
|
||||
correctedPOSIXLocale = static_cast<char *>(uprv_malloc(uprv_strlen(posixID)+1));
|
||||
/* new locale can be 1 char longer than old one if @ -> __ */
|
||||
correctedPOSIXLocale = static_cast<char *>(uprv_malloc(uprv_strlen(posixID)+2));
|
||||
/* Exit on memory allocation error. */
|
||||
if (correctedPOSIXLocale == NULL) {
|
||||
return NULL;
|
||||
@ -1685,7 +1686,7 @@ The leftmost codepage (.xxx) wins.
|
||||
}
|
||||
|
||||
if (uprv_strchr(correctedPOSIXLocale,'_') == NULL) {
|
||||
uprv_strcat(correctedPOSIXLocale, "__"); /* aa@b -> aa__b */
|
||||
uprv_strcat(correctedPOSIXLocale, "__"); /* aa@b -> aa__b (note this can make the new locale 1 char longer) */
|
||||
}
|
||||
else {
|
||||
uprv_strcat(correctedPOSIXLocale, "_"); /* aa_CC@b -> aa_CC_b */
|
||||
|
@ -94,7 +94,7 @@ typedef size_t uintptr_t;
|
||||
# define U_NL_LANGINFO_CODESET CODESET
|
||||
#endif
|
||||
|
||||
#ifdef U_TZSET
|
||||
#if defined(U_TZSET) || defined(U_HAVE_TZSET)
|
||||
/* Use the predefined value. */
|
||||
#elif U_PLATFORM_USES_ONLY_WIN32_API
|
||||
// UWP doesn't support tzset or environment variables for tz
|
||||
@ -132,7 +132,7 @@ typedef size_t uintptr_t;
|
||||
# define U_TIMEZONE timezone
|
||||
#endif
|
||||
|
||||
#ifdef U_TZNAME
|
||||
#if defined(U_TZNAME) || defined(U_HAVE_TZNAME)
|
||||
/* Use the predefined value. */
|
||||
#elif U_PLATFORM_USES_ONLY_WIN32_API
|
||||
/* not usable on all windows platforms */
|
||||
|
@ -53,22 +53,6 @@ uprv_isInvariantString(const char *s, int32_t length);
|
||||
U_INTERNAL UBool U_EXPORT2
|
||||
uprv_isInvariantUString(const UChar *s, int32_t length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
/**
|
||||
* Check if a UnicodeString only contains invariant characters.
|
||||
* See utypes.h for details.
|
||||
*
|
||||
* @param s Input string.
|
||||
* @return TRUE if s contains only invariant characters.
|
||||
*/
|
||||
U_INTERNAL inline UBool U_EXPORT2
|
||||
uprv_isInvariantUnicodeString(const icu::UnicodeString &s) {
|
||||
return uprv_isInvariantUString(icu::toUCharPtr(s.getBuffer()), s.length());
|
||||
}
|
||||
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/**
|
||||
* \def U_UPPER_ORDINAL
|
||||
* Get the ordinal number of an uppercase invariant character
|
||||
|
@ -1460,9 +1460,9 @@ _appendLDMLExtensionAsKeywords(const char* ldmlext, ExtensionListEntry** appendT
|
||||
kwd->value = pType;
|
||||
|
||||
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
|
||||
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
// duplicate keyword is allowed, Only the first
|
||||
// is honored.
|
||||
uprv_free(kwd);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2416,6 +2416,23 @@ uloc_forLanguageTag(const char* langtag,
|
||||
int32_t localeIDCapacity,
|
||||
int32_t* parsedLength,
|
||||
UErrorCode* status) {
|
||||
return ulocimp_forLanguageTag(
|
||||
langtag,
|
||||
-1,
|
||||
localeID,
|
||||
localeIDCapacity,
|
||||
parsedLength,
|
||||
status);
|
||||
}
|
||||
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
ulocimp_forLanguageTag(const char* langtag,
|
||||
int32_t tagLen,
|
||||
char* localeID,
|
||||
int32_t localeIDCapacity,
|
||||
int32_t* parsedLength,
|
||||
UErrorCode* status) {
|
||||
ULanguageTag *lt;
|
||||
int32_t reslen = 0;
|
||||
const char *subtag, *p;
|
||||
@ -2423,7 +2440,7 @@ uloc_forLanguageTag(const char* langtag,
|
||||
int32_t i, n;
|
||||
UBool noRegion = TRUE;
|
||||
|
||||
lt = ultag_parse(langtag, -1, parsedLength, status);
|
||||
lt = ultag_parse(langtag, tagLen, parsedLength, status);
|
||||
if (U_FAILURE(*status)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -61,6 +61,38 @@ ulocimp_getCountry(const char *localeID,
|
||||
char *country, int32_t countryCapacity,
|
||||
const char **pEnd);
|
||||
|
||||
/**
|
||||
* Returns a locale ID for the specified BCP47 language tag string.
|
||||
* If the specified language tag contains any ill-formed subtags,
|
||||
* the first such subtag and all following subtags are ignored.
|
||||
* <p>
|
||||
* This implements the 'Language-Tag' production of BCP47, and so
|
||||
* supports grandfathered (regular and irregular) as well as private
|
||||
* use language tags. Private use tags are represented as 'x-whatever',
|
||||
* and grandfathered tags are converted to their canonical replacements
|
||||
* where they exist. Note that a few grandfathered tags have no modern
|
||||
* replacement, these will be converted using the fallback described in
|
||||
* the first paragraph, so some information might be lost.
|
||||
* @param langtag the input BCP47 language tag.
|
||||
* @param tagLen the length of langtag, or -1 to call uprv_strlen().
|
||||
* @param localeID the output buffer receiving a locale ID for the
|
||||
* specified BCP47 language tag.
|
||||
* @param localeIDCapacity the size of the locale ID output buffer.
|
||||
* @param parsedLength if not NULL, successfully parsed length
|
||||
* for the input language tag is set.
|
||||
* @param err error information if receiving the locald ID
|
||||
* failed.
|
||||
* @return the length of the locale ID.
|
||||
* @internal ICU 63
|
||||
*/
|
||||
U_STABLE int32_t U_EXPORT2
|
||||
ulocimp_forLanguageTag(const char* langtag,
|
||||
int32_t tagLen,
|
||||
char* localeID,
|
||||
int32_t localeIDCapacity,
|
||||
int32_t* parsedLength,
|
||||
UErrorCode* err);
|
||||
|
||||
/**
|
||||
* Get the region to use for supplemental data lookup. Uses
|
||||
* (1) any region specified by locale tag "rg"; if none then
|
||||
|
@ -32,6 +32,8 @@
|
||||
#define LOCID_H
|
||||
|
||||
#include "unicode/bytestream.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/strenum.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "unicode/uobject.h"
|
||||
@ -282,6 +284,14 @@ public:
|
||||
*/
|
||||
Locale(const Locale& other);
|
||||
|
||||
/**
|
||||
* Move constructor; might leave source in bogus state.
|
||||
* This locale will have the same contents that the source locale had.
|
||||
*
|
||||
* @param other The Locale object being moved in.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Locale(Locale&& other) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
@ -298,6 +308,17 @@ public:
|
||||
*/
|
||||
Locale& operator=(const Locale& other);
|
||||
|
||||
/**
|
||||
* Move assignment operator; might leave source in bogus state.
|
||||
* This locale will have the same contents that the source locale had.
|
||||
* The behavior is undefined if *this and the source are the same object.
|
||||
*
|
||||
* @param other The Locale object being moved in.
|
||||
* @return *this
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Locale& operator=(Locale&& other) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Checks if two locale keys are the same.
|
||||
*
|
||||
@ -483,6 +504,69 @@ public:
|
||||
*/
|
||||
const char * getBaseName() const;
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
/**
|
||||
* Add the likely subtags for this Locale, per the algorithm described
|
||||
* in the following CLDR technical report:
|
||||
*
|
||||
* http://www.unicode.org/reports/tr35/#Likely_Subtags
|
||||
*
|
||||
* If this Locale is already in the maximal form, or not valid, or there is
|
||||
* no data available for maximization, the Locale will be unchanged.
|
||||
*
|
||||
* For example, "und-Zzzz" cannot be maximized, since there is no
|
||||
* reasonable maximization.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* "en" maximizes to "en_Latn_US"
|
||||
*
|
||||
* "de" maximizes to "de_Latn_US"
|
||||
*
|
||||
* "sr" maximizes to "sr_Cyrl_RS"
|
||||
*
|
||||
* "sh" maximizes to "sr_Latn_RS" (Note this will not reverse.)
|
||||
*
|
||||
* "zh_Hani" maximizes to "zh_Hans_CN" (Note this will not reverse.)
|
||||
*
|
||||
* @param status error information if maximizing this Locale failed.
|
||||
* If this Locale is not well-formed, the error code is
|
||||
* U_ILLEGAL_ARGUMENT_ERROR.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void addLikelySubtags(UErrorCode& status);
|
||||
|
||||
/**
|
||||
* Minimize the subtags for this Locale, per the algorithm described
|
||||
* in the following CLDR technical report:
|
||||
*
|
||||
* http://www.unicode.org/reports/tr35/#Likely_Subtags
|
||||
*
|
||||
* If this Locale is already in the minimal form, or not valid, or there is
|
||||
* no data available for minimization, the Locale will be unchanged.
|
||||
*
|
||||
* Since the minimization algorithm relies on proper maximization, see the
|
||||
* comments for addLikelySubtags for reasons why there might not be any
|
||||
* data.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* "en_Latn_US" minimizes to "en"
|
||||
*
|
||||
* "de_Latn_US" minimizes to "de"
|
||||
*
|
||||
* "sr_Cyrl_RS" minimizes to "sr"
|
||||
*
|
||||
* "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the
|
||||
* script, and minimizing to "zh" would imply "zh_Hans_CN".)
|
||||
*
|
||||
* @param status error information if maximizing this Locale failed.
|
||||
* If this Locale is not well-formed, the error code is
|
||||
* U_ILLEGAL_ARGUMENT_ERROR.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void minimizeSubtags(UErrorCode& status);
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* Gets the list of keywords for the specified locale.
|
||||
@ -490,13 +574,64 @@ public:
|
||||
* @param status the status code
|
||||
* @return pointer to StringEnumeration class, or NULL if there are no keywords.
|
||||
* Client must dispose of it by calling delete.
|
||||
* @see getKeywords
|
||||
* @stable ICU 2.8
|
||||
*/
|
||||
StringEnumeration * createKeywords(UErrorCode &status) const;
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* Gets the list of Unicode keywords for the specified locale.
|
||||
*
|
||||
* @param status the status code
|
||||
* @return pointer to StringEnumeration class, or NULL if there are no keywords.
|
||||
* Client must dispose of it by calling delete.
|
||||
* @see getUnicodeKeywords
|
||||
* @draft ICU 63
|
||||
*/
|
||||
StringEnumeration * createUnicodeKeywords(UErrorCode &status) const;
|
||||
|
||||
/**
|
||||
* Gets the set of keywords for this Locale.
|
||||
*
|
||||
* A wrapper to call createKeywords() and write the resulting
|
||||
* keywords as standard strings (or compatible objects) into any kind of
|
||||
* container that can be written to by an STL style output iterator.
|
||||
*
|
||||
* @param iterator an STL style output iterator to write the keywords to.
|
||||
* @param status error information if creating set of keywords failed.
|
||||
* @return a set of strings with all keywords.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
template<typename StringClass, typename OutputIterator>
|
||||
inline void getKeywords(OutputIterator iterator, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Gets the set of Unicode keywords for this Locale.
|
||||
*
|
||||
* A wrapper to call createUnicodeKeywords() and write the resulting
|
||||
* keywords as standard strings (or compatible objects) into any kind of
|
||||
* container that can be written to by an STL style output iterator.
|
||||
*
|
||||
* @param iterator an STL style output iterator to write the keywords to.
|
||||
* @param status error information if creating set of keywords failed.
|
||||
* @return a set of strings with all keywords.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
template<typename StringClass, typename OutputIterator>
|
||||
inline void getUnicodeKeywords(OutputIterator iterator, UErrorCode& status) const;
|
||||
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* Gets the value for a keyword.
|
||||
*
|
||||
* This uses legacy keyword=value pairs, like "collation=phonebook".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword for which we want the value. Case insensitive.
|
||||
* @param buffer The buffer to receive the keyword value.
|
||||
* @param bufferCapacity The capacity of receiving buffer
|
||||
@ -507,12 +642,81 @@ public:
|
||||
*/
|
||||
int32_t getKeywordValue(const char* keywordName, char *buffer, int32_t bufferCapacity, UErrorCode &status) const;
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
/**
|
||||
* Gets the value for a keyword.
|
||||
*
|
||||
* This uses legacy keyword=value pairs, like "collation=phonebook".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword for which we want the value.
|
||||
* @param sink the sink to receive the keyword value.
|
||||
* @param status error information if getting the value failed.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Gets the value for a keyword.
|
||||
*
|
||||
* This uses legacy keyword=value pairs, like "collation=phonebook".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword for which we want the value.
|
||||
* @param status error information if getting the value failed.
|
||||
* @return the keyword value.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
template<typename StringClass>
|
||||
inline StringClass getKeywordValue(StringPiece keywordName, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Gets the Unicode value for a Unicode keyword.
|
||||
*
|
||||
* This uses Unicode key-value pairs, like "co-phonebk".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword for which we want the value.
|
||||
* @param sink the sink to receive the keyword value.
|
||||
* @param status error information if getting the value failed.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void getUnicodeKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Gets the Unicode value for a Unicode keyword.
|
||||
*
|
||||
* This uses Unicode key-value pairs, like "co-phonebk".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword for which we want the value.
|
||||
* @param status error information if getting the value failed.
|
||||
* @return the keyword value.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
template<typename StringClass>
|
||||
inline StringClass getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const;
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* Sets or removes the value for a keyword.
|
||||
*
|
||||
* For removing all keywords, use getBaseName(),
|
||||
* and construct a new Locale if it differs from getName().
|
||||
*
|
||||
* This uses legacy keyword=value pairs, like "collation=phonebook".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword to be set. Case insensitive.
|
||||
* @param keywordValue value of the keyword to be set. If 0-length or
|
||||
* NULL, will result in the keyword being removed. No error is given if
|
||||
@ -523,6 +727,48 @@ public:
|
||||
*/
|
||||
void setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status);
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
/**
|
||||
* Sets or removes the value for a keyword.
|
||||
*
|
||||
* For removing all keywords, use getBaseName(),
|
||||
* and construct a new Locale if it differs from getName().
|
||||
*
|
||||
* This uses legacy keyword=value pairs, like "collation=phonebook".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword to be set.
|
||||
* @param keywordValue value of the keyword to be set. If 0-length or
|
||||
* NULL, will result in the keyword being removed. No error is given if
|
||||
* that keyword does not exist.
|
||||
* @param status Returns any error information while performing this operation.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void setKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status);
|
||||
|
||||
/**
|
||||
* Sets or removes the Unicode value for a Unicode keyword.
|
||||
*
|
||||
* For removing all keywords, use getBaseName(),
|
||||
* and construct a new Locale if it differs from getName().
|
||||
*
|
||||
* This uses Unicode key-value pairs, like "co-phonebk".
|
||||
*
|
||||
* ICU4C doesn't do automatic conversion between legacy and Unicode
|
||||
* keywords and values in getters and setters (as opposed to ICU4J).
|
||||
*
|
||||
* @param keywordName name of the keyword to be set.
|
||||
* @param keywordValue value of the keyword to be set. If 0-length or
|
||||
* NULL, will result in the keyword being removed. No error is given if
|
||||
* that keyword does not exist.
|
||||
* @param status Returns any error information while performing this operation.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
void setUnicodeKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status);
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* returns the locale's three-letter language code, as specified
|
||||
* in ISO draft standard ISO-639-2.
|
||||
@ -867,6 +1113,62 @@ Locale::getName() const
|
||||
return fullName;
|
||||
}
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
||||
template<typename StringClass, typename OutputIterator> inline void
|
||||
Locale::getKeywords(OutputIterator iterator, UErrorCode& status) const
|
||||
{
|
||||
LocalPointer<StringEnumeration> keys(createKeywords(status));
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
int32_t resultLength;
|
||||
const char* buffer = keys->next(&resultLength, status);
|
||||
if (U_FAILURE(status) || buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
*iterator++ = StringClass(buffer, resultLength);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename StringClass, typename OutputIterator> inline void
|
||||
Locale::getUnicodeKeywords(OutputIterator iterator, UErrorCode& status) const
|
||||
{
|
||||
LocalPointer<StringEnumeration> keys(createUnicodeKeywords(status));
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
int32_t resultLength;
|
||||
const char* buffer = keys->next(&resultLength, status);
|
||||
if (U_FAILURE(status) || buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
*iterator++ = StringClass(buffer, resultLength);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename StringClass> inline StringClass
|
||||
Locale::getKeywordValue(StringPiece keywordName, UErrorCode& status) const
|
||||
{
|
||||
StringClass result;
|
||||
StringByteSink<StringClass> sink(&result);
|
||||
getKeywordValue(keywordName, sink, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename StringClass> inline StringClass
|
||||
Locale::getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const
|
||||
{
|
||||
StringClass result;
|
||||
StringByteSink<StringClass> sink(&result);
|
||||
getUnicodeKeywordValue(keywordName, sink, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
inline UBool
|
||||
Locale::isBogus(void) const {
|
||||
return fIsBogus;
|
||||
|
@ -420,6 +420,9 @@
|
||||
#ifndef __has_cpp_attribute
|
||||
# define __has_cpp_attribute(x) 0
|
||||
#endif
|
||||
#ifndef __has_declspec_attribute
|
||||
# define __has_declspec_attribute(x) 0
|
||||
#endif
|
||||
#ifndef __has_builtin
|
||||
# define __has_builtin(x) 0
|
||||
#endif
|
||||
@ -783,6 +786,8 @@ namespace std {
|
||||
/* Use the predefined value. */
|
||||
#elif defined(U_STATIC_IMPLEMENTATION)
|
||||
# define U_EXPORT
|
||||
#elif defined(_MSC_VER) || (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport))
|
||||
# define U_EXPORT __declspec(dllexport)
|
||||
#elif defined(__GNUC__)
|
||||
# define U_EXPORT __attribute__((visibility("default")))
|
||||
#elif (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x550) \
|
||||
@ -790,8 +795,6 @@ namespace std {
|
||||
# define U_EXPORT __global
|
||||
/*#elif defined(__HP_aCC) || defined(__HP_cc)
|
||||
# define U_EXPORT __declspec(dllexport)*/
|
||||
#elif defined(_MSC_VER)
|
||||
# define U_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define U_EXPORT
|
||||
#endif
|
||||
@ -807,7 +810,7 @@ namespace std {
|
||||
|
||||
#ifdef U_IMPORT
|
||||
/* Use the predefined value. */
|
||||
#elif defined(_MSC_VER)
|
||||
#elif defined(_MSC_VER) || (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport))
|
||||
/* Windows needs to export/import data. */
|
||||
# define U_IMPORT __declspec(dllimport)
|
||||
#else
|
||||
|
@ -59,7 +59,7 @@
|
||||
</taskdef>
|
||||
</target>
|
||||
<!-- target for generating ICU data -->
|
||||
<target name="all" depends="locales, collation, rbnf, supplementalData, metadata, metaZones, windowsZones, likelySubtags, plurals, numberingSystems, translit, brkitr, keyTypeData, genderList, dayPeriods" />
|
||||
<target name="all" depends="locales, collation, rbnf, supplementalData, metadata, metaZones, windowsZones, likelySubtags, plurals, pluralRanges, numberingSystems, translit, brkitr, keyTypeData, genderList, dayPeriods" />
|
||||
<!-- parallel target -->
|
||||
<target name="pall" depends="init">
|
||||
<parallel threadsPerProcessor ="1">
|
||||
@ -258,6 +258,18 @@
|
||||
</run>
|
||||
</cldr-build>
|
||||
</target>
|
||||
<target name="pluralRanges" depends="init,setup" description="builds pluralRanges.txt from pluralRanges.xml">
|
||||
<cldr-build toolName="org.unicode.cldr.icu.NewLdml2IcuConverter" destFile="pluralRanges.txt" noArgs="true">
|
||||
<!-- launch the tool and generate the data after reading the config file -->
|
||||
<run>
|
||||
<args>
|
||||
<arg name="-s" value="${env.CLDR_DIR}/common/supplemental" />
|
||||
<arg name="-d" value="${env.ICU4C_DIR}/source/data/misc"/>
|
||||
<arg name="-t" value="pluralRanges"/>
|
||||
</args>
|
||||
</run>
|
||||
</cldr-build>
|
||||
</target>
|
||||
<target name="numberingSystems" depends="init,setup" description="builds numberingSystems.txt from numberingSystems.xml">
|
||||
<cldr-build toolName="org.unicode.cldr.icu.NewLdml2IcuConverter" destFile="numberingSystems.txt" noArgs="true">
|
||||
<!-- launch the tool and generate the data after reading the config file -->
|
||||
@ -414,6 +426,9 @@
|
||||
<fileset id="plurals" dir="${env.ICU4C_DIR}/source/data/misc">
|
||||
<include name="plurals.txt" />
|
||||
</fileset>
|
||||
<fileset id="pluralRanges" dir="${env.ICU4C_DIR}/source/data/misc">
|
||||
<include name="pluralRanges.txt" />
|
||||
</fileset>
|
||||
<fileset id="numberingSystems" dir="${env.ICU4C_DIR}/source/data/misc">
|
||||
<include name="numberingSystems.txt" />
|
||||
</fileset>
|
||||
|
@ -6,7 +6,7 @@
|
||||
// Corporation and others. All Rights Reserved.
|
||||
//---------------------------------------------------------
|
||||
// Build tool: com.ibm.icu.dev.tool.currency.NumericCodeData
|
||||
// Build date: 2018-06-06T22:39:58Z
|
||||
// Build date: 2018-09-17T17:52:05Z
|
||||
//---------------------------------------------------------
|
||||
// >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! <<
|
||||
// >> !!! >>> DO NOT EDIT <<< !!! <<
|
||||
@ -270,9 +270,11 @@ currencyNumericCodes:table(nofallback){
|
||||
UYN:int{858}
|
||||
UYP:int{858}
|
||||
UYU:int{858}
|
||||
UYW:int{927}
|
||||
UZS:int{860}
|
||||
VEB:int{862}
|
||||
VEF:int{937}
|
||||
VES:int{928}
|
||||
VNC:int{704}
|
||||
VND:int{704}
|
||||
VUV:int{548}
|
||||
|
@ -28,4 +28,4 @@ MISC_SOURCE = \
|
||||
zoneinfo64.txt supplementalData.txt likelySubtags.txt plurals.txt \
|
||||
numberingSystems.txt icuver.txt icustd.txt metadata.txt metaZones.txt \
|
||||
windowsZones.txt keyTypeData.txt timezoneTypes.txt currencyNumericCodes.txt \
|
||||
genderList.txt dayPeriods.txt
|
||||
genderList.txt dayPeriods.txt pluralRanges.txt
|
||||
|
981
icu4c/source/data/misc/pluralRanges.txt
Normal file
981
icu4c/source/data/misc/pluralRanges.txt
Normal file
@ -0,0 +1,981 @@
|
||||
// © 2016 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
pluralRanges:table(nofallback){
|
||||
locales{
|
||||
af{"set05"}
|
||||
ak{"set02"}
|
||||
am{"set01"}
|
||||
ar{"set18"}
|
||||
as{"set01"}
|
||||
az{"set04"}
|
||||
be{"set15"}
|
||||
bg{"set05"}
|
||||
bn{"set01"}
|
||||
bs{"set11"}
|
||||
ca{"set05"}
|
||||
cs{"set14"}
|
||||
cy{"set17"}
|
||||
da{"set06"}
|
||||
de{"set04"}
|
||||
el{"set04"}
|
||||
en{"set05"}
|
||||
es{"set05"}
|
||||
et{"set05"}
|
||||
eu{"set05"}
|
||||
fa{"set02"}
|
||||
fi{"set05"}
|
||||
fil{"set06"}
|
||||
fr{"set01"}
|
||||
ga{"set16"}
|
||||
gl{"set04"}
|
||||
gsw{"set04"}
|
||||
gu{"set01"}
|
||||
he{"set13"}
|
||||
hi{"set01"}
|
||||
hr{"set11"}
|
||||
hu{"set04"}
|
||||
hy{"set01"}
|
||||
id{"set00"}
|
||||
io{"set05"}
|
||||
is{"set06"}
|
||||
it{"set04"}
|
||||
ja{"set00"}
|
||||
ka{"set03"}
|
||||
kk{"set04"}
|
||||
km{"set00"}
|
||||
kn{"set01"}
|
||||
ko{"set00"}
|
||||
ky{"set04"}
|
||||
lo{"set00"}
|
||||
lt{"set15"}
|
||||
lv{"set09"}
|
||||
mk{"set08"}
|
||||
ml{"set04"}
|
||||
mn{"set04"}
|
||||
mr{"set01"}
|
||||
ms{"set00"}
|
||||
my{"set00"}
|
||||
nb{"set05"}
|
||||
ne{"set04"}
|
||||
nl{"set04"}
|
||||
or{"set02"}
|
||||
pa{"set06"}
|
||||
pl{"set14"}
|
||||
ps{"set01"}
|
||||
pt{"set01"}
|
||||
ro{"set10"}
|
||||
ru{"set15"}
|
||||
scn{"set04"}
|
||||
sd{"set02"}
|
||||
si{"set07"}
|
||||
sk{"set14"}
|
||||
sl{"set12"}
|
||||
sq{"set04"}
|
||||
sr{"set11"}
|
||||
sv{"set05"}
|
||||
sw{"set04"}
|
||||
ta{"set04"}
|
||||
te{"set04"}
|
||||
th{"set00"}
|
||||
tk{"set04"}
|
||||
tr{"set04"}
|
||||
ug{"set04"}
|
||||
uk{"set15"}
|
||||
ur{"set05"}
|
||||
uz{"set04"}
|
||||
vi{"set00"}
|
||||
yue{"set00"}
|
||||
zh{"set00"}
|
||||
zu{"set01"}
|
||||
}
|
||||
rules{
|
||||
set00{
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set01{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set02{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set03{
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set04{
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set05{
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set06{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set07{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set08{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set09{
|
||||
{
|
||||
"zero",
|
||||
"zero",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"zero",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"zero",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set10{
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"one",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set11{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set12{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"one",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"one",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set13{
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"many",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"two",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set14{
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set15{
|
||||
{
|
||||
"one",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set16{
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set17{
|
||||
{
|
||||
"zero",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"one",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"two",
|
||||
"two",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
set18{
|
||||
{
|
||||
"zero",
|
||||
"one",
|
||||
"zero",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"two",
|
||||
"zero",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"zero",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"one",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"two",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"few",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"many",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"one",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"two",
|
||||
"other",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"few",
|
||||
"few",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"many",
|
||||
"many",
|
||||
}
|
||||
{
|
||||
"other",
|
||||
"other",
|
||||
"other",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -111,9 +111,9 @@ double-conversion-fast-dtoa.o double-conversion-strtod.o \
|
||||
numparse_stringsegment.o numparse_parsednumber.o numparse_impl.o \
|
||||
numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \
|
||||
numparse_affixes.o numparse_compositions.o numparse_validators.o \
|
||||
numrange_fluent.o numrange_impl.o \
|
||||
erarules.o
|
||||
|
||||
|
||||
## Header files to install
|
||||
HEADERS = $(srcdir)/unicode/*.h
|
||||
|
||||
|
@ -3797,7 +3797,7 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
|
||||
Locale max = Locale::createFromName(maxLocaleID);
|
||||
useLocale = Locale(max.getLanguage(),max.getCountry());
|
||||
} else {
|
||||
useLocale = Locale(desiredLocale);
|
||||
useLocale = desiredLocale;
|
||||
}
|
||||
|
||||
/* The code here is somewhat of a hack, since week data and weekend data aren't really tied to
|
||||
|
@ -1057,12 +1057,19 @@ UBool DecimalFormat::areSignificantDigitsUsed() const {
|
||||
|
||||
void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {
|
||||
// These are the default values from the old implementation.
|
||||
int32_t minSig = useSignificantDigits ? 1 : -1;
|
||||
int32_t maxSig = useSignificantDigits ? 6 : -1;
|
||||
if (fields->properties->minimumSignificantDigits == minSig &&
|
||||
fields->properties->maximumSignificantDigits == maxSig) {
|
||||
if (useSignificantDigits) {
|
||||
if (fields->properties->minimumSignificantDigits != -1 ||
|
||||
fields->properties->maximumSignificantDigits != -1) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (fields->properties->minimumSignificantDigits == -1 &&
|
||||
fields->properties->maximumSignificantDigits == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int32_t minSig = useSignificantDigits ? 1 : -1;
|
||||
int32_t maxSig = useSignificantDigits ? 6 : -1;
|
||||
fields->properties->minimumSignificantDigits = minSig;
|
||||
fields->properties->maximumSignificantDigits = maxSig;
|
||||
touchNoError();
|
||||
|
@ -99,13 +99,13 @@ static int32_t compareEncodedDateWithYMD(int encoded, int year, int month, int d
|
||||
}
|
||||
}
|
||||
|
||||
EraRules::EraRules(int32_t *startDates, int32_t numEras)
|
||||
: startDates(startDates), numEras(numEras) {
|
||||
EraRules::EraRules(LocalArray<int32_t>& eraStartDates, int32_t numEras)
|
||||
: numEras(numEras) {
|
||||
startDates.moveFrom(eraStartDates);
|
||||
initCurrentEra();
|
||||
}
|
||||
|
||||
EraRules::~EraRules() {
|
||||
uprv_free(startDates);
|
||||
}
|
||||
|
||||
EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEra, UErrorCode& status) {
|
||||
@ -124,33 +124,32 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
|
||||
int32_t numEras = ures_getSize(rb.getAlias());
|
||||
int32_t firstTentativeIdx = MAX_INT32;
|
||||
|
||||
int32_t *startDates = (int32_t*)uprv_malloc(numEras * sizeof(int32_t));
|
||||
if (startDates == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
LocalArray<int32_t> startDates(new int32_t[numEras], status);
|
||||
if (U_FAILURE(status)) {
|
||||
return nullptr;
|
||||
}
|
||||
uprv_memset(startDates, 0, numEras * sizeof(int32_t));
|
||||
uprv_memset(startDates.getAlias(), 0 , numEras * sizeof(int32_t));
|
||||
|
||||
while (ures_hasNext(rb.getAlias())) {
|
||||
LocalUResourceBundlePointer eraRuleRes(ures_getNextResource(rb.getAlias(), nullptr, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
const char *eraIdxStr = ures_getKey(eraRuleRes.getAlias());
|
||||
char *endp;
|
||||
int32_t eraIdx = (int32_t)strtol(eraIdxStr, &endp, 10);
|
||||
if ((size_t)(endp - eraIdxStr) != uprv_strlen(eraIdxStr)) {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
if (eraIdx < 0 || eraIdx >= numEras) {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
if (isSet(startDates[eraIdx])) {
|
||||
// start date of the index was already set
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UBool hasName = TRUE;
|
||||
@ -159,17 +158,17 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
|
||||
while (ures_hasNext(eraRuleRes.getAlias())) {
|
||||
LocalUResourceBundlePointer res(ures_getNextResource(eraRuleRes.getAlias(), nullptr, &status));
|
||||
if (U_FAILURE(status)) {
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
const char *key = ures_getKey(res.getAlias());
|
||||
if (uprv_strcmp(key, "start") == 0) {
|
||||
const int32_t *fields = ures_getIntVector(res.getAlias(), &len, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
if (len != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]);
|
||||
} else if (uprv_strcmp(key, "named") == 0) {
|
||||
@ -193,20 +192,20 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
|
||||
// This implementation does not support end only rule for eras other than
|
||||
// the first one.
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
U_ASSERT(eraIdx == 0);
|
||||
startDates[eraIdx] = MIN_ENCODED_START;
|
||||
} else {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasName) {
|
||||
if (eraIdx >= firstTentativeIdx) {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
goto error;
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (eraIdx < firstTentativeIdx) {
|
||||
@ -226,10 +225,6 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
}
|
||||
return result;
|
||||
|
||||
error:
|
||||
uprv_free(startDates);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EraRules::getStartDate(int32_t eraIdx, int32_t (&fields)[3], UErrorCode& status) const {
|
||||
|
@ -12,6 +12,16 @@
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
// Export an explicit template instantiation of LocalArray used as a data member of EraRules.
|
||||
// When building DLLs for Windows this is required even though no direct access leaks out of the i18n library.
|
||||
// See digitlst.h, pluralaffix.h, datefmt.h, and others for similar examples.
|
||||
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
|
||||
// Ignore warning 4661 as LocalPointerBase does not use operator== or operator!=
|
||||
#pragma warning(suppress: 4661)
|
||||
template class U_I18N_API LocalPointerBase<int32_t>;
|
||||
template class U_I18N_API LocalArray<int32_t>;
|
||||
#endif
|
||||
|
||||
class U_I18N_API EraRules : public UMemory {
|
||||
public:
|
||||
~EraRules();
|
||||
@ -66,11 +76,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
EraRules(int32_t *startDates, int32_t numEra);
|
||||
EraRules(LocalArray<int32_t>& eraStartDates, int32_t numEra);
|
||||
|
||||
void initCurrentEra();
|
||||
|
||||
int32_t *startDates;
|
||||
LocalArray<int32_t> startDates;
|
||||
int32_t numEras;
|
||||
int32_t currentEra;
|
||||
};
|
||||
|
@ -289,6 +289,8 @@
|
||||
<ClCompile Include="numparse_affixes.cpp" />
|
||||
<ClCompile Include="numparse_compositions.cpp" />
|
||||
<ClCompile Include="numparse_validators.cpp" />
|
||||
<ClCompile Include="numrange_fluent.cpp" />
|
||||
<ClCompile Include="numrange_impl.cpp" />
|
||||
<ClCompile Include="numfmt.cpp" />
|
||||
<ClCompile Include="numsys.cpp" />
|
||||
<ClCompile Include="olsontz.cpp" />
|
||||
@ -552,6 +554,7 @@
|
||||
<ClInclude Include="numparse_validators.h" />
|
||||
<ClInclude Include="numparse_types.h" />
|
||||
<ClInclude Include="numparse_utils.h" />
|
||||
<ClInclude Include="numrange_impl.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="i18n.rc" />
|
||||
|
@ -624,6 +624,12 @@
|
||||
<ClCompile Include="numparse_validators.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="numrange_fluent.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="numrange_impl.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dayperiodrules.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
@ -914,6 +920,9 @@
|
||||
<ClInclude Include="numparse_utils.h">
|
||||
<Filter>formatting</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="numrange_impl.h">
|
||||
<Filter>formatting</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="olsontz.h">
|
||||
<Filter>formatting</Filter>
|
||||
</ClInclude>
|
||||
|
@ -396,6 +396,8 @@
|
||||
<ClCompile Include="numparse_affixes.cpp" />
|
||||
<ClCompile Include="numparse_compositions.cpp" />
|
||||
<ClCompile Include="numparse_validators.cpp" />
|
||||
<ClCompile Include="numrange_fluent.cpp" />
|
||||
<ClCompile Include="numrange_impl.cpp" />
|
||||
<ClCompile Include="numfmt.cpp" />
|
||||
<ClCompile Include="numsys.cpp" />
|
||||
<ClCompile Include="olsontz.cpp" />
|
||||
@ -657,6 +659,7 @@
|
||||
<ClInclude Include="numparse_validators.h" />
|
||||
<ClInclude Include="numparse_types.h" />
|
||||
<ClInclude Include="numparse_utils.h" />
|
||||
<ClInclude Include="numrange_impl.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="i18n.rc" />
|
||||
|
@ -347,12 +347,15 @@ IndianCalendar::inDaylightTime(UErrorCode& status) const
|
||||
return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE);
|
||||
}
|
||||
|
||||
// default century
|
||||
const UDate IndianCalendar::fgSystemDefaultCentury = DBL_MIN;
|
||||
const int32_t IndianCalendar::fgSystemDefaultCenturyYear = -1;
|
||||
|
||||
UDate IndianCalendar::fgSystemDefaultCenturyStart = DBL_MIN;
|
||||
int32_t IndianCalendar::fgSystemDefaultCenturyStartYear = -1;
|
||||
/**
|
||||
* The system maintains a static default century start date and Year. They are
|
||||
* initialized the first time they are used. Once the system default century date
|
||||
* and year are set, they do not change.
|
||||
*/
|
||||
static UDate gSystemDefaultCenturyStart = DBL_MIN;
|
||||
static int32_t gSystemDefaultCenturyStartYear = -1;
|
||||
static icu::UInitOnce gSystemDefaultCenturyInit = U_INITONCE_INITIALIZER;
|
||||
|
||||
|
||||
UBool IndianCalendar::haveDefaultCentury() const
|
||||
@ -360,65 +363,12 @@ UBool IndianCalendar::haveDefaultCentury() const
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UDate IndianCalendar::defaultCenturyStart() const
|
||||
{
|
||||
return internalGetDefaultCenturyStart();
|
||||
}
|
||||
|
||||
int32_t IndianCalendar::defaultCenturyStartYear() const
|
||||
{
|
||||
return internalGetDefaultCenturyStartYear();
|
||||
}
|
||||
|
||||
UDate
|
||||
IndianCalendar::internalGetDefaultCenturyStart() const
|
||||
{
|
||||
// lazy-evaluate systemDefaultCenturyStart
|
||||
UBool needsUpdate;
|
||||
{
|
||||
Mutex m;
|
||||
needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury);
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
initializeSystemDefaultCentury();
|
||||
}
|
||||
|
||||
// use defaultCenturyStart unless it's the flag value;
|
||||
// then use systemDefaultCenturyStart
|
||||
|
||||
return fgSystemDefaultCenturyStart;
|
||||
}
|
||||
|
||||
int32_t
|
||||
IndianCalendar::internalGetDefaultCenturyStartYear() const
|
||||
{
|
||||
// lazy-evaluate systemDefaultCenturyStartYear
|
||||
UBool needsUpdate;
|
||||
{
|
||||
Mutex m;
|
||||
|
||||
needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury);
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
initializeSystemDefaultCentury();
|
||||
}
|
||||
|
||||
// use defaultCenturyStart unless it's the flag value;
|
||||
// then use systemDefaultCenturyStartYear
|
||||
|
||||
return fgSystemDefaultCenturyStartYear;
|
||||
}
|
||||
|
||||
void
|
||||
IndianCalendar::initializeSystemDefaultCentury()
|
||||
static void U_CALLCONV
|
||||
initializeSystemDefaultCentury()
|
||||
{
|
||||
// initialize systemDefaultCentury and systemDefaultCenturyYear based
|
||||
// on the current time. They'll be set to 80 years before
|
||||
// the current time.
|
||||
// No point in locking as it should be idempotent.
|
||||
if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
IndianCalendar calendar ( Locale ( "@calendar=Indian" ), status);
|
||||
@ -429,19 +379,30 @@ IndianCalendar::initializeSystemDefaultCentury()
|
||||
UDate newStart = calendar.getTime ( status );
|
||||
int32_t newYear = calendar.get ( UCAL_YEAR, status );
|
||||
|
||||
gSystemDefaultCenturyStart = newStart;
|
||||
gSystemDefaultCenturyStartYear = newYear;
|
||||
}
|
||||
// We have no recourse upon failure.
|
||||
}
|
||||
|
||||
|
||||
UDate
|
||||
IndianCalendar::defaultCenturyStart() const
|
||||
{
|
||||
Mutex m;
|
||||
|
||||
fgSystemDefaultCenturyStart = newStart;
|
||||
fgSystemDefaultCenturyStartYear = newYear;
|
||||
}
|
||||
// lazy-evaluate systemDefaultCenturyStart
|
||||
umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury);
|
||||
return gSystemDefaultCenturyStart;
|
||||
}
|
||||
|
||||
// We have no recourse upon failure unless we want to propagate the failure
|
||||
// out.
|
||||
}
|
||||
int32_t
|
||||
IndianCalendar::defaultCenturyStartYear() const
|
||||
{
|
||||
// lazy-evaluate systemDefaultCenturyStartYear
|
||||
umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury);
|
||||
return gSystemDefaultCenturyStartYear;
|
||||
}
|
||||
|
||||
|
||||
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IndianCalendar)
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
@ -68,7 +68,7 @@ U_NAMESPACE_BEGIN
|
||||
*/
|
||||
|
||||
|
||||
class IndianCalendar : public Calendar {
|
||||
class U_I18N_API IndianCalendar : public Calendar {
|
||||
public:
|
||||
/**
|
||||
* Useful constants for IndianCalendar.
|
||||
@ -274,10 +274,10 @@ public:
|
||||
* @return The class ID for all objects of this class.
|
||||
* @internal
|
||||
*/
|
||||
U_I18N_API static UClassID U_EXPORT2 getStaticClassID(void);
|
||||
static UClassID U_EXPORT2 getStaticClassID(void);
|
||||
|
||||
/**
|
||||
* return the calendar type, "buddhist".
|
||||
* return the calendar type, "indian".
|
||||
*
|
||||
* @return calendar type
|
||||
* @internal
|
||||
@ -320,49 +320,6 @@ protected:
|
||||
* @internal
|
||||
*/
|
||||
virtual int32_t defaultCenturyStartYear() const;
|
||||
|
||||
private: // default century stuff.
|
||||
/**
|
||||
* The system maintains a static default century start date. This is initialized
|
||||
* the first time it is used. Before then, it is set to SYSTEM_DEFAULT_CENTURY to
|
||||
* indicate an uninitialized state. Once the system default century date and year
|
||||
* are set, they do not change.
|
||||
*/
|
||||
static UDate fgSystemDefaultCenturyStart;
|
||||
|
||||
/**
|
||||
* See documentation for systemDefaultCenturyStart.
|
||||
*/
|
||||
static int32_t fgSystemDefaultCenturyStartYear;
|
||||
|
||||
/**
|
||||
* Default value that indicates the defaultCenturyStartYear is unitialized
|
||||
*/
|
||||
static const int32_t fgSystemDefaultCenturyYear;
|
||||
|
||||
/**
|
||||
* start of default century, as a date
|
||||
*/
|
||||
static const UDate fgSystemDefaultCentury;
|
||||
|
||||
/**
|
||||
* Returns the beginning date of the 100-year window that dates
|
||||
* with 2-digit years are considered to fall within.
|
||||
*/
|
||||
UDate internalGetDefaultCenturyStart(void) const;
|
||||
|
||||
/**
|
||||
* Returns the first year of the 100-year window that dates with
|
||||
* 2-digit years are considered to fall within.
|
||||
*/
|
||||
int32_t internalGetDefaultCenturyStartYear(void) const;
|
||||
|
||||
/**
|
||||
* Initializes the 100-year window that dates with 2-digit years
|
||||
* are considered to fall within so that its start date is 80 years
|
||||
* before the current time.
|
||||
*/
|
||||
static void initializeSystemDefaultCentury(void);
|
||||
};
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
@ -59,11 +59,19 @@ static void U_CALLCONV initializeEras(UErrorCode &status) {
|
||||
// By default, such tentative era is disabled.
|
||||
|
||||
// 1. Environment variable ICU_ENABLE_TENTATIVE_ERA=true or false
|
||||
// 2. Windows registry (TBD)
|
||||
|
||||
UBool includeTentativeEra = FALSE;
|
||||
|
||||
#if U_PLATFORM_HAS_WINUWP_API == 0
|
||||
#if U_PLATFORM_HAS_WINUWP_API == 1
|
||||
// UWP doesn't allow access to getenv(), but we can call GetEnvironmentVariableW to do the same thing.
|
||||
UChar varName[26] = {};
|
||||
u_charsToUChars(TENTATIVE_ERA_VAR_NAME, varName, static_cast<int32_t>(uprv_strlen(TENTATIVE_ERA_VAR_NAME)));
|
||||
WCHAR varValue[5] = {};
|
||||
DWORD ret = GetEnvironmentVariableW(reinterpret_cast<WCHAR*>(varName), varValue, UPRV_LENGTHOF(varValue));
|
||||
if ((ret == 4) && (_wcsicmp(varValue, L"true") == 0)) {
|
||||
includeTentativeEra = TRUE;
|
||||
}
|
||||
#else
|
||||
char *envVarVal = getenv(TENTATIVE_ERA_VAR_NAME);
|
||||
if (envVarVal != NULL && uprv_stricmp(envVarVal, "true") == 0) {
|
||||
includeTentativeEra = TRUE;
|
||||
|
@ -288,8 +288,7 @@ ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& e
|
||||
}
|
||||
|
||||
ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
|
||||
Locale tempLocale = locale;
|
||||
const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode);
|
||||
const ListFormatInternal* listFormatInternal = getListFormatInternal(locale, style, errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -273,13 +273,13 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
|
||||
if (U_FAILURE(status)) { return; }
|
||||
|
||||
// Treat zero as if it had magnitude 0
|
||||
int magnitude;
|
||||
int32_t magnitude;
|
||||
if (quantity.isZero()) {
|
||||
magnitude = 0;
|
||||
micros.rounder.apply(quantity, status);
|
||||
} else {
|
||||
// TODO: Revisit chooseMultiplierAndApply
|
||||
int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status);
|
||||
int32_t multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status);
|
||||
magnitude = quantity.isZero() ? 0 : quantity.getMagnitude();
|
||||
magnitude -= multiplier;
|
||||
}
|
||||
|
@ -1154,8 +1154,31 @@ const char16_t* DecimalQuantity::checkHealth() const {
|
||||
}
|
||||
|
||||
bool DecimalQuantity::operator==(const DecimalQuantity& other) const {
|
||||
// FIXME: Make a faster implementation.
|
||||
return toString() == other.toString();
|
||||
bool basicEquals =
|
||||
scale == other.scale
|
||||
&& precision == other.precision
|
||||
&& flags == other.flags
|
||||
&& lOptPos == other.lOptPos
|
||||
&& lReqPos == other.lReqPos
|
||||
&& rReqPos == other.rReqPos
|
||||
&& rOptPos == other.rOptPos
|
||||
&& isApproximate == other.isApproximate;
|
||||
if (!basicEquals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (precision == 0) {
|
||||
return true;
|
||||
} else if (isApproximate) {
|
||||
return origDouble == other.origDouble && origDelta == other.origDelta;
|
||||
} else {
|
||||
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
|
||||
if (getDigit(m) != other.getDigit(m)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeString DecimalQuantity::toString() const {
|
||||
|
@ -363,6 +363,7 @@ UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const NFS<UNF>& other)
|
||||
// No additional fields to assign
|
||||
}
|
||||
|
||||
// Make default copy constructor call the NumberFormatterSettings copy constructor.
|
||||
UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(UNF&& src) U_NOEXCEPT
|
||||
: UNF(static_cast<NFS<UNF>&&>(src)) {}
|
||||
|
||||
@ -383,6 +384,7 @@ UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(UNF&& src) U_N
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Make default copy constructor call the NumberFormatterSettings copy constructor.
|
||||
LocalizedNumberFormatter::LocalizedNumberFormatter(const LNF& other)
|
||||
: LNF(static_cast<const NFS<LNF>&>(other)) {}
|
||||
|
||||
@ -405,7 +407,8 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(NFS<LNF>&& src) U_NOEXCEPT
|
||||
|
||||
LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(const LNF& other) {
|
||||
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
|
||||
// No additional fields to assign (let call count and compiled formatter reset to defaults)
|
||||
// Reset to default values.
|
||||
clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -417,13 +420,18 @@ LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(LNF&& src) U_NOEXC
|
||||
// Formatter is compiled
|
||||
lnfMoveHelper(static_cast<LNF&&>(src));
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LocalizedNumberFormatter::clear() {
|
||||
// Reset to default values.
|
||||
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
|
||||
umtx_storeRelease(*callCount, 0);
|
||||
delete fCompiled;
|
||||
fCompiled = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LocalizedNumberFormatter::lnfMoveHelper(LNF&& src) {
|
||||
// Copy over the compiled formatter and set call count to INT32_MIN as in computeCompiled().
|
||||
@ -431,6 +439,7 @@ void LocalizedNumberFormatter::lnfMoveHelper(LNF&& src) {
|
||||
// The bits themselves appear to be platform-dependent, so copying them might not be safe.
|
||||
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
|
||||
umtx_storeRelease(*callCount, INT32_MIN);
|
||||
delete fCompiled;
|
||||
fCompiled = src.fCompiled;
|
||||
// Reset the source object to leave it in a safe state.
|
||||
auto* srcCallCount = reinterpret_cast<u_atomic_int32_t*>(src.fUnsafeCallCount);
|
||||
@ -657,9 +666,9 @@ LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErro
|
||||
|
||||
void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const {
|
||||
if (computeCompiled(status)) {
|
||||
fCompiled->apply(results->quantity, results->string, status);
|
||||
fCompiled->format(results->quantity, results->string, status);
|
||||
} else {
|
||||
NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status);
|
||||
NumberFormatterImpl::formatStatic(fMacros, results->quantity, results->string, status);
|
||||
}
|
||||
}
|
||||
|
||||
@ -706,7 +715,11 @@ bool LocalizedNumberFormatter::computeCompiled(UErrorCode& status) const {
|
||||
|
||||
if (currentCount == fMacros.threshold && fMacros.threshold > 0) {
|
||||
// Build the data structure and then use it (slow to fast path).
|
||||
const NumberFormatterImpl* compiled = NumberFormatterImpl::fromMacros(fMacros, status);
|
||||
const NumberFormatterImpl* compiled = new NumberFormatterImpl(fMacros, status);
|
||||
if (compiled == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return false;
|
||||
}
|
||||
U_ASSERT(fCompiled == nullptr);
|
||||
const_cast<LocalizedNumberFormatter*>(this)->fCompiled = compiled;
|
||||
umtx_storeRelease(*callCount, INT32_MIN);
|
||||
|
@ -67,14 +67,18 @@ getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& sta
|
||||
MicroPropsGenerator::~MicroPropsGenerator() = default;
|
||||
|
||||
|
||||
NumberFormatterImpl* NumberFormatterImpl::fromMacros(const MacroProps& macros, UErrorCode& status) {
|
||||
return new NumberFormatterImpl(macros, true, status);
|
||||
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
|
||||
: NumberFormatterImpl(macros, true, status) {
|
||||
}
|
||||
|
||||
void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity& inValue,
|
||||
int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
|
||||
NumberStringBuilder& outString, UErrorCode& status) {
|
||||
NumberFormatterImpl impl(macros, false, status);
|
||||
impl.applyUnsafe(inValue, outString, status);
|
||||
MicroProps& micros = impl.preProcessUnsafe(inValue, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
int32_t length = writeNumber(micros, inValue, outString, 0, status);
|
||||
length += writeAffixes(micros, outString, 0, length, status);
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
|
||||
@ -89,22 +93,40 @@ int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int
|
||||
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
|
||||
// See MicroProps::processQuantity() for details.
|
||||
|
||||
void NumberFormatterImpl::apply(DecimalQuantity& inValue, NumberStringBuilder& outString,
|
||||
int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, NumberStringBuilder& outString,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
MicroProps micros;
|
||||
if (!fMicroPropsGenerator) { return; }
|
||||
fMicroPropsGenerator->processQuantity(inValue, micros, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
microsToString(micros, inValue, outString, status);
|
||||
preProcess(inValue, micros, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
int32_t length = writeNumber(micros, inValue, outString, 0, status);
|
||||
length += writeAffixes(micros, outString, 0, length, status);
|
||||
return length;
|
||||
}
|
||||
|
||||
void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
|
||||
UErrorCode& status) {
|
||||
void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (fMicroPropsGenerator == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
}
|
||||
fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
|
||||
microsOut.rounder.apply(inValue, status);
|
||||
microsOut.integerWidth.apply(inValue, status);
|
||||
}
|
||||
|
||||
MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
|
||||
if (U_FAILURE(status)) {
|
||||
return fMicros; // must always return a value
|
||||
}
|
||||
if (fMicroPropsGenerator == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return fMicros; // must always return a value
|
||||
}
|
||||
fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
microsToString(fMicros, inValue, outString, status);
|
||||
fMicros.rounder.apply(inValue, status);
|
||||
fMicros.integerWidth.apply(inValue, status);
|
||||
return fMicros;
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form plural,
|
||||
@ -115,7 +137,7 @@ int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form
|
||||
const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
|
||||
modifier->apply(outString, 0, 0, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
return modifier->getPrefixLength(status);
|
||||
return modifier->getPrefixLength();
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
|
||||
@ -126,7 +148,7 @@ int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural
|
||||
fPatternModifier->setNumberProperties(signum, plural);
|
||||
fPatternModifier->apply(outString, 0, 0, status);
|
||||
if (U_FAILURE(status)) { return 0; }
|
||||
return fPatternModifier->getPrefixLength(status);
|
||||
return fPatternModifier->getPrefixLength();
|
||||
}
|
||||
|
||||
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
|
||||
@ -344,7 +366,6 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (isCldrUnit) {
|
||||
fLongNameHandler.adoptInstead(
|
||||
new LongNameHandler(
|
||||
LongNameHandler::forMeasureUnit(
|
||||
macros.locale,
|
||||
macros.unit,
|
||||
@ -352,17 +373,16 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
status)));
|
||||
status));
|
||||
chain = fLongNameHandler.getAlias();
|
||||
} else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
|
||||
fLongNameHandler.adoptInstead(
|
||||
new LongNameHandler(
|
||||
LongNameHandler::forCurrencyLongNames(
|
||||
macros.locale,
|
||||
currency,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
status)));
|
||||
status));
|
||||
chain = fLongNameHandler.getAlias();
|
||||
} else {
|
||||
// No outer modifier required
|
||||
@ -404,50 +424,46 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Local
|
||||
return fRules.getAlias();
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::microsToString(const MicroProps& micros, DecimalQuantity& quantity,
|
||||
NumberStringBuilder& string, UErrorCode& status) {
|
||||
micros.rounder.apply(quantity, status);
|
||||
micros.integerWidth.apply(quantity, status);
|
||||
int32_t length = writeNumber(micros, quantity, string, status);
|
||||
// NOTE: When range formatting is added, these modifiers can bubble up.
|
||||
// For now, apply them all here at once.
|
||||
int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, NumberStringBuilder& string,
|
||||
int32_t start, int32_t end, UErrorCode& status) {
|
||||
// Always apply the inner modifier (which is "strong").
|
||||
length += micros.modInner->apply(string, 0, length, status);
|
||||
int32_t length = micros.modInner->apply(string, start, end, status);
|
||||
if (micros.padding.isValid()) {
|
||||
length += micros.padding
|
||||
.padAndApply(*micros.modMiddle, *micros.modOuter, string, 0, length, status);
|
||||
.padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
|
||||
} else {
|
||||
length += micros.modMiddle->apply(string, 0, length, status);
|
||||
length += micros.modOuter->apply(string, 0, length, status);
|
||||
length += micros.modMiddle->apply(string, start, length + end, status);
|
||||
length += micros.modOuter->apply(string, start, length + end, status);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
|
||||
NumberStringBuilder& string, UErrorCode& status) {
|
||||
NumberStringBuilder& string, int32_t index,
|
||||
UErrorCode& status) {
|
||||
int32_t length = 0;
|
||||
if (quantity.isInfinite()) {
|
||||
length += string.insert(
|
||||
length,
|
||||
length + index,
|
||||
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
|
||||
UNUM_INTEGER_FIELD,
|
||||
status);
|
||||
|
||||
} else if (quantity.isNaN()) {
|
||||
length += string.insert(
|
||||
length,
|
||||
length + index,
|
||||
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
|
||||
UNUM_INTEGER_FIELD,
|
||||
status);
|
||||
|
||||
} else {
|
||||
// Add the integer digits
|
||||
length += writeIntegerDigits(micros, quantity, string, status);
|
||||
length += writeIntegerDigits(micros, quantity, string, length + index, status);
|
||||
|
||||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
|
||||
length += string.insert(
|
||||
length,
|
||||
length + index,
|
||||
micros.useCurrency ? micros.symbols->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
|
||||
.symbols
|
||||
@ -458,21 +474,22 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuanti
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
length += writeFractionDigits(micros, quantity, string, status);
|
||||
length += writeFractionDigits(micros, quantity, string, length + index, status);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
|
||||
NumberStringBuilder& string, UErrorCode& status) {
|
||||
NumberStringBuilder& string, int32_t index,
|
||||
UErrorCode& status) {
|
||||
int length = 0;
|
||||
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
|
||||
for (int i = 0; i < integerCount; i++) {
|
||||
// Add grouping separator
|
||||
if (micros.grouping.groupAtPosition(i, quantity)) {
|
||||
length += string.insert(
|
||||
0,
|
||||
index,
|
||||
micros.useCurrency ? micros.symbols->getSymbol(
|
||||
DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
|
||||
: micros.symbols->getSymbol(
|
||||
@ -484,20 +501,21 @@ int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, Decima
|
||||
// Get and append the next digit value
|
||||
int8_t nextDigit = quantity.getDigit(i);
|
||||
length += utils::insertDigitFromSymbols(
|
||||
string, 0, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
|
||||
string, index, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
|
||||
NumberStringBuilder& string, UErrorCode& status) {
|
||||
NumberStringBuilder& string, int32_t index,
|
||||
UErrorCode& status) {
|
||||
int length = 0;
|
||||
int fractionCount = -quantity.getLowerDisplayMagnitude();
|
||||
for (int i = 0; i < fractionCount; i++) {
|
||||
// Get and append the next digit value
|
||||
int8_t nextDigit = quantity.getDigit(-i - 1);
|
||||
length += utils::insertDigitFromSymbols(
|
||||
string, string.length(), nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
|
||||
string, length + index, nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ class NumberFormatterImpl : public UMemory {
|
||||
* Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly.
|
||||
* The caller owns the returned NumberFormatterImpl.
|
||||
*/
|
||||
static NumberFormatterImpl *fromMacros(const MacroProps ¯os, UErrorCode &status);
|
||||
NumberFormatterImpl(const MacroProps ¯os, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
static void
|
||||
applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString,
|
||||
static int32_t
|
||||
formatStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString,
|
||||
UErrorCode &status);
|
||||
|
||||
/**
|
||||
@ -51,7 +51,12 @@ class NumberFormatterImpl : public UMemory {
|
||||
/**
|
||||
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||||
*/
|
||||
void apply(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const;
|
||||
int32_t format(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Like format(), but saves the result into an output MicroProps without additional processing.
|
||||
*/
|
||||
void preProcess(DecimalQuantity& inValue, MicroProps& microsOut, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Like getPrefixSuffixStatic() but uses the safe compiled object.
|
||||
@ -59,6 +64,19 @@ class NumberFormatterImpl : public UMemory {
|
||||
int32_t getPrefixSuffix(int8_t signum, StandardPlural::Form plural, NumberStringBuilder& outString,
|
||||
UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
* This method formats only the main number, not affixes.
|
||||
*/
|
||||
static int32_t writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
|
||||
NumberStringBuilder& string, int32_t index, UErrorCode& status);
|
||||
|
||||
/**
|
||||
* Adds the affixes. Intended to be called immediately after formatNumber.
|
||||
*/
|
||||
static int32_t writeAffixes(const MicroProps& micros, NumberStringBuilder& string, int32_t start,
|
||||
int32_t end, UErrorCode& status);
|
||||
|
||||
private:
|
||||
// Head of the MicroPropsGenerator linked list:
|
||||
const MicroPropsGenerator *fMicroPropsGenerator = nullptr;
|
||||
@ -85,7 +103,7 @@ class NumberFormatterImpl : public UMemory {
|
||||
|
||||
NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status);
|
||||
|
||||
void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status);
|
||||
MicroProps& preProcessUnsafe(DecimalQuantity &inValue, UErrorCode &status);
|
||||
|
||||
int32_t getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
|
||||
NumberStringBuilder& outString, UErrorCode& status);
|
||||
@ -113,31 +131,13 @@ class NumberFormatterImpl : public UMemory {
|
||||
const MicroPropsGenerator *
|
||||
macrosToMicroGenerator(const MacroProps ¯os, bool safe, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
*
|
||||
* @param micros
|
||||
* The MicroProps after the quantity has been consumed. Will not be mutated.
|
||||
* @param quantity
|
||||
* The DecimalQuantity to be rendered. May be mutated.
|
||||
* @param string
|
||||
* The output string. Will be mutated.
|
||||
*/
|
||||
static int32_t
|
||||
microsToString(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeNumber(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeIntegerDigits(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
int32_t index, UErrorCode &status);
|
||||
|
||||
static int32_t
|
||||
writeFractionDigits(const MicroProps µs, DecimalQuantity &quantity, NumberStringBuilder &string,
|
||||
UErrorCode &status);
|
||||
int32_t index, UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
@ -39,7 +39,7 @@ static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
|
||||
|
||||
static UnicodeString getWithPlural(
|
||||
const UnicodeString* strings,
|
||||
int32_t plural,
|
||||
StandardPlural::Form plural,
|
||||
UErrorCode& status) {
|
||||
UnicodeString result = strings[plural];
|
||||
if (result.isBogus()) {
|
||||
@ -156,7 +156,7 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
|
||||
|
||||
} // namespace
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler*
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
@ -173,20 +173,28 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, c
|
||||
}
|
||||
}
|
||||
|
||||
LongNameHandler result(rules, parent);
|
||||
auto* result = new LongNameHandler(rules, parent);
|
||||
if (result == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
// TODO: What field to use for units?
|
||||
simpleFormatsToModifiers(simpleFormats, UNUM_FIELD_COUNT, result.fModifiers, status);
|
||||
result->simpleFormatsToModifiers(simpleFormats, UNUM_FIELD_COUNT, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler*
|
||||
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
auto* result = new LongNameHandler(rules, parent);
|
||||
if (result == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
UnicodeString primaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, primaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
@ -213,46 +221,52 @@ LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, con
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, result.fModifiers, status);
|
||||
result->multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy,
|
||||
LongNameHandler* LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy,
|
||||
const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent,
|
||||
UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
auto* result = new LongNameHandler(rules, parent);
|
||||
if (result == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
}
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(loc, currency, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, result.fModifiers, status);
|
||||
if (U_FAILURE(status)) { return nullptr; }
|
||||
result->simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status) {
|
||||
UErrorCode &status) {
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString simpleFormat = getWithPlural(simpleFormats, i, status);
|
||||
StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
|
||||
UnicodeString simpleFormat = getWithPlural(simpleFormats, plural, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compiledFormatter, field, false);
|
||||
fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, 0, plural});
|
||||
}
|
||||
}
|
||||
|
||||
void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status) {
|
||||
Field field, UErrorCode &status) {
|
||||
SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString leadFormat = getWithPlural(leadFormats, i, status);
|
||||
StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
|
||||
UnicodeString leadFormat = getWithPlural(leadFormats, plural, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
UnicodeString compoundFormat;
|
||||
trailCompiled.format(leadFormat, compoundFormat, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compoundCompiled, field, false);
|
||||
fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, 0, plural});
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,4 +279,8 @@ void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &mic
|
||||
micros.modOuter = &fModifiers[utils::getStandardPlural(rules, copy)];
|
||||
}
|
||||
|
||||
const Modifier* LongNameHandler::getModifier(int8_t /*signum*/, StandardPlural::Form plural) const {
|
||||
return &fModifiers[plural];
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
@ -14,13 +14,13 @@
|
||||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
||||
class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public UMemory {
|
||||
public:
|
||||
static LongNameHandler
|
||||
static LongNameHandler*
|
||||
forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static LongNameHandler
|
||||
static LongNameHandler*
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
@ -28,6 +28,8 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
void
|
||||
processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
|
||||
const PluralRules *rules;
|
||||
@ -36,15 +38,14 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
|
||||
: rules(rules), parent(parent) {}
|
||||
|
||||
static LongNameHandler
|
||||
static LongNameHandler*
|
||||
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status);
|
||||
static void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status);
|
||||
void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status);
|
||||
void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
@ -225,8 +225,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
|
||||
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
|
||||
if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
|
||||
// For the purposes of rounding, get the original min/max int/frac, since the local
|
||||
// variables
|
||||
// have been manipulated for display purposes.
|
||||
// variables have been manipulated for display purposes.
|
||||
int maxInt_ = properties.maximumIntegerDigits;
|
||||
int minInt_ = properties.minimumIntegerDigits;
|
||||
int minFrac_ = properties.minimumFractionDigits;
|
||||
int maxFrac_ = properties.maximumFractionDigits;
|
||||
@ -237,9 +237,15 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
|
||||
// Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
|
||||
macros.precision = Precision::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode);
|
||||
} else {
|
||||
// All other scientific patterns, which mean round to minInt+maxFrac
|
||||
macros.precision = Precision::constructSignificant(
|
||||
minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode);
|
||||
int maxSig_ = minInt_ + maxFrac_;
|
||||
// Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
|
||||
if (maxInt_ > minInt_ && minInt_ > 1) {
|
||||
minInt_ = 1;
|
||||
}
|
||||
int minSig_ = minInt_ + minFrac_;
|
||||
// To avoid regression, maxSig is not reset when minInt_ set to 1.
|
||||
// TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
|
||||
macros.precision = Precision::constructSignificant(minSig_, maxSig_).withMode(roundingMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,21 @@ void U_CALLCONV initDefaultCurrencySpacing(UErrorCode &status) {
|
||||
|
||||
Modifier::~Modifier() = default;
|
||||
|
||||
Modifier::Parameters::Parameters()
|
||||
: obj(nullptr) {}
|
||||
|
||||
Modifier::Parameters::Parameters(
|
||||
const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural)
|
||||
: obj(_obj), signum(_signum), plural(_plural) {}
|
||||
|
||||
ModifierStore::~ModifierStore() = default;
|
||||
|
||||
AdoptingModifierStore::~AdoptingModifierStore() {
|
||||
for (const Modifier *mod : mods) {
|
||||
delete mod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
|
||||
UErrorCode &status) const {
|
||||
@ -62,13 +77,11 @@ int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex,
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t ConstantAffixModifier::getPrefixLength(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t ConstantAffixModifier::getPrefixLength() const {
|
||||
return fPrefix.length();
|
||||
}
|
||||
|
||||
int32_t ConstantAffixModifier::getCodePointCount(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t ConstantAffixModifier::getCodePointCount() const {
|
||||
return fPrefix.countChar32() + fSuffix.countChar32();
|
||||
}
|
||||
|
||||
@ -76,8 +89,38 @@ bool ConstantAffixModifier::isStrong() const {
|
||||
return fStrong;
|
||||
}
|
||||
|
||||
bool ConstantAffixModifier::containsField(UNumberFormatFields field) const {
|
||||
(void)field;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConstantAffixModifier::getParameters(Parameters& output) const {
|
||||
(void)output;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
}
|
||||
|
||||
bool ConstantAffixModifier::semanticallyEquivalent(const Modifier& other) const {
|
||||
auto* _other = dynamic_cast<const ConstantAffixModifier*>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return fPrefix == _other->fPrefix
|
||||
&& fSuffix == _other->fSuffix
|
||||
&& fField == _other->fField
|
||||
&& fStrong == _other->fStrong;
|
||||
}
|
||||
|
||||
|
||||
SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong)
|
||||
: fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong) {
|
||||
: SimpleModifier(simpleFormatter, field, strong, {}) {}
|
||||
|
||||
SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong,
|
||||
const Modifier::Parameters parameters)
|
||||
: fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong),
|
||||
fParameters(parameters) {
|
||||
int32_t argLimit = SimpleFormatter::getArgumentLimit(
|
||||
fCompiledPattern.getBuffer(), fCompiledPattern.length());
|
||||
if (argLimit == 0) {
|
||||
@ -90,15 +133,19 @@ SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field fie
|
||||
} else {
|
||||
U_ASSERT(argLimit == 1);
|
||||
if (fCompiledPattern.charAt(1) != 0) {
|
||||
// Found prefix
|
||||
fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
|
||||
fSuffixOffset = 3 + fPrefixLength;
|
||||
} else {
|
||||
// No prefix
|
||||
fPrefixLength = 0;
|
||||
fSuffixOffset = 2;
|
||||
}
|
||||
if (3 + fPrefixLength < fCompiledPattern.length()) {
|
||||
// Found suffix
|
||||
fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT;
|
||||
} else {
|
||||
// No suffix
|
||||
fSuffixLength = 0;
|
||||
}
|
||||
}
|
||||
@ -113,13 +160,11 @@ int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int ri
|
||||
return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
|
||||
}
|
||||
|
||||
int32_t SimpleModifier::getPrefixLength(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t SimpleModifier::getPrefixLength() const {
|
||||
return fPrefixLength;
|
||||
}
|
||||
|
||||
int32_t SimpleModifier::getCodePointCount(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t SimpleModifier::getCodePointCount() const {
|
||||
int32_t count = 0;
|
||||
if (fPrefixLength > 0) {
|
||||
count += fCompiledPattern.countChar32(2, fPrefixLength);
|
||||
@ -134,10 +179,35 @@ bool SimpleModifier::isStrong() const {
|
||||
return fStrong;
|
||||
}
|
||||
|
||||
bool SimpleModifier::containsField(UNumberFormatFields field) const {
|
||||
(void)field;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimpleModifier::getParameters(Parameters& output) const {
|
||||
output = fParameters;
|
||||
}
|
||||
|
||||
bool SimpleModifier::semanticallyEquivalent(const Modifier& other) const {
|
||||
auto* _other = dynamic_cast<const SimpleModifier*>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (fParameters.obj != nullptr) {
|
||||
return fParameters.obj == _other->fParameters.obj;
|
||||
}
|
||||
return fCompiledPattern == _other->fCompiledPattern
|
||||
&& fField == _other->fField
|
||||
&& fStrong == _other->fStrong;
|
||||
}
|
||||
|
||||
|
||||
int32_t
|
||||
SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
|
||||
Field field, UErrorCode &status) const {
|
||||
if (fSuffixOffset == -1) {
|
||||
if (fSuffixOffset == -1 && fPrefixLength + fSuffixLength > 0) {
|
||||
// There is no argument for the inner number; overwrite the entire segment with our string.
|
||||
return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
|
||||
} else {
|
||||
@ -157,6 +227,65 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int32_t
|
||||
SimpleModifier::formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result,
|
||||
int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength,
|
||||
Field field, UErrorCode& status) {
|
||||
const UnicodeString& compiledPattern = compiled.compiledPattern;
|
||||
int32_t argLimit = SimpleFormatter::getArgumentLimit(
|
||||
compiledPattern.getBuffer(), compiledPattern.length());
|
||||
if (argLimit != 2) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return 0;
|
||||
}
|
||||
int32_t offset = 1; // offset into compiledPattern
|
||||
int32_t length = 0; // chars added to result
|
||||
|
||||
int32_t prefixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (prefixLength < ARG_NUM_LIMIT) {
|
||||
// No prefix
|
||||
prefixLength = 0;
|
||||
} else {
|
||||
prefixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + prefixLength, field, status);
|
||||
offset += prefixLength;
|
||||
length += prefixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int32_t infixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (infixLength < ARG_NUM_LIMIT) {
|
||||
// No infix
|
||||
infixLength = 0;
|
||||
} else {
|
||||
infixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + infixLength, field, status);
|
||||
offset += infixLength;
|
||||
length += infixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int32_t suffixLength;
|
||||
if (offset == compiledPattern.length()) {
|
||||
// No suffix
|
||||
suffixLength = 0;
|
||||
} else {
|
||||
suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
|
||||
offset++;
|
||||
result.insert(index + length, compiledPattern, offset, offset + suffixLength, field, status);
|
||||
length += suffixLength;
|
||||
}
|
||||
|
||||
*outPrefixLength = prefixLength;
|
||||
*outSuffixLength = suffixLength;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
|
||||
UErrorCode &status) const {
|
||||
int32_t length = output.insert(leftIndex, fPrefix, status);
|
||||
@ -171,13 +300,11 @@ int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftI
|
||||
return length;
|
||||
}
|
||||
|
||||
int32_t ConstantMultiFieldModifier::getPrefixLength(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t ConstantMultiFieldModifier::getPrefixLength() const {
|
||||
return fPrefix.length();
|
||||
}
|
||||
|
||||
int32_t ConstantMultiFieldModifier::getCodePointCount(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t ConstantMultiFieldModifier::getCodePointCount() const {
|
||||
return fPrefix.codePointCount() + fSuffix.codePointCount();
|
||||
}
|
||||
|
||||
@ -185,6 +312,29 @@ bool ConstantMultiFieldModifier::isStrong() const {
|
||||
return fStrong;
|
||||
}
|
||||
|
||||
bool ConstantMultiFieldModifier::containsField(UNumberFormatFields field) const {
|
||||
return fPrefix.containsField(field) || fSuffix.containsField(field);
|
||||
}
|
||||
|
||||
void ConstantMultiFieldModifier::getParameters(Parameters& output) const {
|
||||
output = fParameters;
|
||||
}
|
||||
|
||||
bool ConstantMultiFieldModifier::semanticallyEquivalent(const Modifier& other) const {
|
||||
auto* _other = dynamic_cast<const ConstantMultiFieldModifier*>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (fParameters.obj != nullptr) {
|
||||
return fParameters.obj == _other->fParameters.obj;
|
||||
}
|
||||
return fPrefix.contentEquals(_other->fPrefix)
|
||||
&& fSuffix.contentEquals(_other->fSuffix)
|
||||
&& fOverwrite == _other->fOverwrite
|
||||
&& fStrong == _other->fStrong;
|
||||
}
|
||||
|
||||
|
||||
CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
|
||||
const NumberStringBuilder &suffix,
|
||||
bool overwrite,
|
||||
|
@ -31,12 +31,18 @@ class U_I18N_API ConstantAffixModifier : public Modifier, public UObject {
|
||||
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getPrefixLength() const U_OVERRIDE;
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getCodePointCount() const U_OVERRIDE;
|
||||
|
||||
bool isStrong() const U_OVERRIDE;
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE;
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
UnicodeString fPrefix;
|
||||
UnicodeString fSuffix;
|
||||
@ -52,21 +58,30 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
|
||||
public:
|
||||
SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong);
|
||||
|
||||
SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong,
|
||||
const Modifier::Parameters parameters);
|
||||
|
||||
// Default constructor for LongNameHandler.h
|
||||
SimpleModifier();
|
||||
|
||||
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getPrefixLength() const U_OVERRIDE;
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getCodePointCount() const U_OVERRIDE;
|
||||
|
||||
bool isStrong() const U_OVERRIDE;
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE;
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
|
||||
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
|
||||
* NumberStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
|
||||
*
|
||||
* <p>
|
||||
* Formats a value that is already stored inside the StringBuilder <code>result</code> between the indices
|
||||
@ -88,13 +103,30 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
|
||||
formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex, Field field,
|
||||
UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
|
||||
* I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other.
|
||||
*
|
||||
* <p>
|
||||
* Applies the compiled two-argument pattern to the NumberStringBuilder.
|
||||
*
|
||||
* <p>
|
||||
* This method is optimized for the case where the prefix and suffix are often empty, such as
|
||||
* in the range pattern like "{0}-{1}".
|
||||
*/
|
||||
static int32_t
|
||||
formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result,
|
||||
int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength,
|
||||
Field field, UErrorCode& status);
|
||||
|
||||
private:
|
||||
UnicodeString fCompiledPattern;
|
||||
Field fField;
|
||||
bool fStrong;
|
||||
int32_t fPrefixLength;
|
||||
int32_t fSuffixOffset;
|
||||
int32_t fSuffixLength;
|
||||
bool fStrong = false;
|
||||
int32_t fPrefixLength = 0;
|
||||
int32_t fSuffixOffset = -1;
|
||||
int32_t fSuffixLength = 0;
|
||||
Modifier::Parameters fParameters;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -103,6 +135,18 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
|
||||
*/
|
||||
class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
|
||||
public:
|
||||
ConstantMultiFieldModifier(
|
||||
const NumberStringBuilder &prefix,
|
||||
const NumberStringBuilder &suffix,
|
||||
bool overwrite,
|
||||
bool strong,
|
||||
const Modifier::Parameters parameters)
|
||||
: fPrefix(prefix),
|
||||
fSuffix(suffix),
|
||||
fOverwrite(overwrite),
|
||||
fStrong(strong),
|
||||
fParameters(parameters) {}
|
||||
|
||||
ConstantMultiFieldModifier(
|
||||
const NumberStringBuilder &prefix,
|
||||
const NumberStringBuilder &suffix,
|
||||
@ -116,12 +160,18 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
|
||||
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getPrefixLength() const U_OVERRIDE;
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getCodePointCount() const U_OVERRIDE;
|
||||
|
||||
bool isStrong() const U_OVERRIDE;
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE;
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
|
||||
|
||||
protected:
|
||||
// NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
|
||||
// value and is treated internally as immutable.
|
||||
@ -129,6 +179,7 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
|
||||
NumberStringBuilder fSuffix;
|
||||
bool fOverwrite;
|
||||
bool fStrong;
|
||||
Modifier::Parameters fParameters;
|
||||
};
|
||||
|
||||
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
|
||||
@ -192,13 +243,11 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE {
|
||||
(void)status;
|
||||
int32_t getPrefixLength() const U_OVERRIDE {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE {
|
||||
(void)status;
|
||||
int32_t getCodePointCount() const U_OVERRIDE {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -206,55 +255,75 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
|
||||
return fStrong;
|
||||
}
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE {
|
||||
(void)field;
|
||||
return false;
|
||||
}
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE {
|
||||
output.obj = nullptr;
|
||||
}
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE {
|
||||
return other.getCodePointCount() == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fStrong;
|
||||
};
|
||||
|
||||
/**
|
||||
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two or more
|
||||
* Modifiers and returns the modifier appropriate for the current situation.
|
||||
* This implementation of ModifierStore adopts Modifer pointers.
|
||||
*/
|
||||
class U_I18N_API ParameterizedModifier : public UMemory {
|
||||
class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
|
||||
public:
|
||||
// NOTE: mods is zero-initialized (to nullptr)
|
||||
ParameterizedModifier() : mods() {
|
||||
}
|
||||
virtual ~AdoptingModifierStore();
|
||||
|
||||
static constexpr StandardPlural::Form DEFAULT_STANDARD_PLURAL = StandardPlural::OTHER;
|
||||
|
||||
AdoptingModifierStore() = default;
|
||||
|
||||
// No copying!
|
||||
ParameterizedModifier(const ParameterizedModifier &other) = delete;
|
||||
AdoptingModifierStore(const AdoptingModifierStore &other) = delete;
|
||||
|
||||
~ParameterizedModifier() {
|
||||
for (const Modifier *mod : mods) {
|
||||
delete mod;
|
||||
}
|
||||
}
|
||||
|
||||
void adoptPositiveNegativeModifiers(
|
||||
const Modifier *positive, const Modifier *zero, const Modifier *negative) {
|
||||
mods[2] = positive;
|
||||
mods[1] = zero;
|
||||
mods[0] = negative;
|
||||
}
|
||||
|
||||
/** The modifier is ADOPTED. */
|
||||
void adoptSignPluralModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) {
|
||||
/**
|
||||
* Sets the Modifier with the specified signum and plural form.
|
||||
*/
|
||||
void adoptModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) {
|
||||
U_ASSERT(mods[getModIndex(signum, plural)] == nullptr);
|
||||
mods[getModIndex(signum, plural)] = mod;
|
||||
}
|
||||
|
||||
/** Returns a reference to the modifier; no ownership change. */
|
||||
const Modifier *getModifier(int8_t signum) const {
|
||||
return mods[signum + 1];
|
||||
/**
|
||||
* Sets the Modifier with the specified signum.
|
||||
* The modifier will apply to all plural forms.
|
||||
*/
|
||||
void adoptModifierWithoutPlural(int8_t signum, const Modifier *mod) {
|
||||
U_ASSERT(mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] == nullptr);
|
||||
mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] = mod;
|
||||
}
|
||||
|
||||
/** Returns a reference to the modifier; no ownership change. */
|
||||
const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const {
|
||||
return mods[getModIndex(signum, plural)];
|
||||
const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE {
|
||||
const Modifier* modifier = mods[getModIndex(signum, plural)];
|
||||
if (modifier == nullptr && plural != DEFAULT_STANDARD_PLURAL) {
|
||||
modifier = mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
|
||||
}
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/** Returns a reference to the modifier; no ownership change. */
|
||||
const Modifier *getModifierWithoutPlural(int8_t signum) const {
|
||||
return mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
|
||||
}
|
||||
|
||||
private:
|
||||
const Modifier *mods[3 * StandardPlural::COUNT];
|
||||
// NOTE: mods is zero-initialized (to nullptr)
|
||||
const Modifier *mods[3 * StandardPlural::COUNT] = {};
|
||||
|
||||
inline static int32_t getModIndex(int8_t signum, StandardPlural::Form plural) {
|
||||
U_ASSERT(signum >= -1 && signum <= 1);
|
||||
U_ASSERT(plural >= 0 && plural < StandardPlural::COUNT);
|
||||
return static_cast<int32_t>(plural) * 3 + (signum + 1);
|
||||
}
|
||||
};
|
||||
|
@ -62,7 +62,7 @@ Padder Padder::forProperties(const DecimalFormatProperties& properties) {
|
||||
int32_t Padder::padAndApply(const Modifier &mod1, const Modifier &mod2,
|
||||
NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const {
|
||||
int32_t modLength = mod1.getCodePointCount(status) + mod2.getCodePointCount(status);
|
||||
int32_t modLength = mod1.getCodePointCount() + mod2.getCodePointCount();
|
||||
int32_t requiredPadding = fWidth - modLength - string.codePointCount();
|
||||
U_ASSERT(leftIndex == 0 &&
|
||||
rightIndex == string.length()); // fix the previous line to remove this assertion
|
||||
|
@ -69,7 +69,7 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
|
||||
StandardPlural::Form::MANY,
|
||||
StandardPlural::Form::OTHER};
|
||||
|
||||
auto pm = new ParameterizedModifier();
|
||||
auto pm = new AdoptingModifierStore();
|
||||
if (pm == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return nullptr;
|
||||
@ -79,11 +79,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
|
||||
// Slower path when we require the plural keyword.
|
||||
for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
|
||||
setNumberProperties(1, plural);
|
||||
pm->adoptSignPluralModifier(1, plural, createConstantModifier(status));
|
||||
pm->adoptModifier(1, plural, createConstantModifier(status));
|
||||
setNumberProperties(0, plural);
|
||||
pm->adoptSignPluralModifier(0, plural, createConstantModifier(status));
|
||||
pm->adoptModifier(0, plural, createConstantModifier(status));
|
||||
setNumberProperties(-1, plural);
|
||||
pm->adoptSignPluralModifier(-1, plural, createConstantModifier(status));
|
||||
pm->adoptModifier(-1, plural, createConstantModifier(status));
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
delete pm;
|
||||
@ -93,12 +93,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
|
||||
} else {
|
||||
// Faster path when plural keyword is not needed.
|
||||
setNumberProperties(1, StandardPlural::Form::COUNT);
|
||||
Modifier* positive = createConstantModifier(status);
|
||||
pm->adoptModifierWithoutPlural(1, createConstantModifier(status));
|
||||
setNumberProperties(0, StandardPlural::Form::COUNT);
|
||||
Modifier* zero = createConstantModifier(status);
|
||||
pm->adoptModifierWithoutPlural(0, createConstantModifier(status));
|
||||
setNumberProperties(-1, StandardPlural::Form::COUNT);
|
||||
Modifier* negative = createConstantModifier(status);
|
||||
pm->adoptPositiveNegativeModifiers(positive, zero, negative);
|
||||
pm->adoptModifierWithoutPlural(-1, createConstantModifier(status));
|
||||
if (U_FAILURE(status)) {
|
||||
delete pm;
|
||||
return nullptr;
|
||||
@ -120,7 +119,7 @@ ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErro
|
||||
}
|
||||
}
|
||||
|
||||
ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules,
|
||||
ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
|
||||
const MicroPropsGenerator* parent)
|
||||
: pm(pm), rules(rules), parent(parent) {}
|
||||
|
||||
@ -132,7 +131,7 @@ void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroP
|
||||
|
||||
void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const {
|
||||
if (rules == nullptr) {
|
||||
micros.modMiddle = pm->getModifier(quantity.signum());
|
||||
micros.modMiddle = pm->getModifierWithoutPlural(quantity.signum());
|
||||
} else {
|
||||
// TODO: Fix this. Avoid the copy.
|
||||
DecimalQuantity copy(quantity);
|
||||
@ -144,7 +143,7 @@ void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity
|
||||
|
||||
const Modifier* ImmutablePatternModifier::getModifier(int8_t signum, StandardPlural::Form plural) const {
|
||||
if (rules == nullptr) {
|
||||
return pm->getModifier(signum);
|
||||
return pm->getModifierWithoutPlural(signum);
|
||||
} else {
|
||||
return pm->getModifier(signum, plural);
|
||||
}
|
||||
@ -204,23 +203,25 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder& output, int32_t leftI
|
||||
return prefixLen + overwriteLen + suffixLen;
|
||||
}
|
||||
|
||||
int32_t MutablePatternModifier::getPrefixLength(UErrorCode& status) const {
|
||||
int32_t MutablePatternModifier::getPrefixLength() const {
|
||||
// The unsafe code path performs self-mutation, so we need a const_cast.
|
||||
// This method needs to be const because it overrides a const method in the parent class.
|
||||
auto nonConstThis = const_cast<MutablePatternModifier*>(this);
|
||||
|
||||
// Enter and exit CharSequence Mode to get the length.
|
||||
UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
|
||||
nonConstThis->prepareAffix(true);
|
||||
int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t MutablePatternModifier::getCodePointCount(UErrorCode& status) const {
|
||||
int32_t MutablePatternModifier::getCodePointCount() const {
|
||||
// The unsafe code path performs self-mutation, so we need a const_cast.
|
||||
// This method needs to be const because it overrides a const method in the parent class.
|
||||
auto nonConstThis = const_cast<MutablePatternModifier*>(this);
|
||||
|
||||
// Render the affixes to get the length
|
||||
UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
|
||||
nonConstThis->prepareAffix(true);
|
||||
int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length
|
||||
nonConstThis->prepareAffix(false);
|
||||
@ -232,6 +233,26 @@ bool MutablePatternModifier::isStrong() const {
|
||||
return fStrong;
|
||||
}
|
||||
|
||||
bool MutablePatternModifier::containsField(UNumberFormatFields field) const {
|
||||
(void)field;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void MutablePatternModifier::getParameters(Parameters& output) const {
|
||||
(void)output;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
}
|
||||
|
||||
bool MutablePatternModifier::semanticallyEquivalent(const Modifier& other) const {
|
||||
(void)other;
|
||||
// This method is not currently used.
|
||||
U_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder& sb, int position, UErrorCode& status) {
|
||||
prepareAffix(true);
|
||||
int length = AffixUtils::unescape(currentAffix, sb, position, *this, status);
|
||||
|
@ -18,13 +18,13 @@
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
// Export an explicit template instantiation of the LocalPointer that is used as a
|
||||
// data member of ParameterizedModifier.
|
||||
// data member of AdoptingModifierStore.
|
||||
// (When building DLLs for Windows this is required.)
|
||||
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
|
||||
// Ignore warning 4661 as LocalPointerBase does not use operator== or operator!=
|
||||
#pragma warning(suppress: 4661)
|
||||
template class U_I18N_API LocalPointerBase<number::impl::ParameterizedModifier>;
|
||||
template class U_I18N_API LocalPointer<number::impl::ParameterizedModifier>;
|
||||
template class U_I18N_API LocalPointerBase<number::impl::AdoptingModifierStore>;
|
||||
template class U_I18N_API LocalPointer<number::impl::AdoptingModifierStore>;
|
||||
#endif
|
||||
|
||||
namespace number {
|
||||
@ -45,10 +45,10 @@ class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public U
|
||||
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const;
|
||||
|
||||
private:
|
||||
ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules,
|
||||
ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
|
||||
const MicroPropsGenerator* parent);
|
||||
|
||||
const LocalPointer<ParameterizedModifier> pm;
|
||||
const LocalPointer<AdoptingModifierStore> pm;
|
||||
const PluralRules* rules;
|
||||
const MicroPropsGenerator* parent;
|
||||
|
||||
@ -178,12 +178,18 @@ class U_I18N_API MutablePatternModifier
|
||||
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getPrefixLength() const U_OVERRIDE;
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getCodePointCount() const U_OVERRIDE;
|
||||
|
||||
bool isStrong() const U_OVERRIDE;
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE;
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Returns the string that substitutes a given symbol type in a pattern.
|
||||
*/
|
||||
|
@ -76,17 +76,16 @@ int32_t ScientificModifier::apply(NumberStringBuilder &output, int32_t /*leftInd
|
||||
return i - rightIndex;
|
||||
}
|
||||
|
||||
int32_t ScientificModifier::getPrefixLength(UErrorCode &status) const {
|
||||
(void)status;
|
||||
int32_t ScientificModifier::getPrefixLength() const {
|
||||
// TODO: Localized exponent separator location.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t ScientificModifier::getCodePointCount(UErrorCode &status) const {
|
||||
(void)status;
|
||||
// This method is not used for strong modifiers.
|
||||
U_ASSERT(false);
|
||||
return 0;
|
||||
int32_t ScientificModifier::getCodePointCount() const {
|
||||
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
|
||||
// The call site only cares about != 0 and != 1.
|
||||
// Return a very large value so that if this method is used elsewhere, we should notice.
|
||||
return 999;
|
||||
}
|
||||
|
||||
bool ScientificModifier::isStrong() const {
|
||||
@ -94,6 +93,27 @@ bool ScientificModifier::isStrong() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScientificModifier::containsField(UNumberFormatFields field) const {
|
||||
(void)field;
|
||||
// This method is not used for inner modifiers.
|
||||
U_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScientificModifier::getParameters(Parameters& output) const {
|
||||
// Not part of any plural sets
|
||||
output.obj = nullptr;
|
||||
}
|
||||
|
||||
bool ScientificModifier::semanticallyEquivalent(const Modifier& other) const {
|
||||
auto* _other = dynamic_cast<const ScientificModifier*>(&other);
|
||||
if (_other == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Check for locale symbols and settings as well? Could be less efficient.
|
||||
return fExponent == _other->fExponent;
|
||||
}
|
||||
|
||||
// Note: Visual Studio does not compile this function without full name space. Why?
|
||||
icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
|
||||
const MicroPropsGenerator *parent) :
|
||||
|
@ -24,12 +24,18 @@ class U_I18N_API ScientificModifier : public UMemory, public Modifier {
|
||||
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
|
||||
UErrorCode &status) const U_OVERRIDE;
|
||||
|
||||
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getPrefixLength() const U_OVERRIDE;
|
||||
|
||||
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
|
||||
int32_t getCodePointCount() const U_OVERRIDE;
|
||||
|
||||
bool isStrong() const U_OVERRIDE;
|
||||
|
||||
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
|
||||
|
||||
void getParameters(Parameters& output) const U_OVERRIDE;
|
||||
|
||||
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
|
||||
|
||||
private:
|
||||
int32_t fExponent;
|
||||
const ScientificHandler *fHandler;
|
||||
|
@ -241,6 +241,9 @@ NumberStringBuilder::insert(int32_t index, const NumberStringBuilder &other, UEr
|
||||
}
|
||||
|
||||
int32_t NumberStringBuilder::prepareForInsert(int32_t index, int32_t count, UErrorCode &status) {
|
||||
U_ASSERT(index >= 0);
|
||||
U_ASSERT(index <= fLength);
|
||||
U_ASSERT(count >= 0);
|
||||
if (index == 0 && fZero - count >= 0) {
|
||||
// Append to start
|
||||
fZero -= count;
|
||||
@ -485,4 +488,13 @@ void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpi
|
||||
}
|
||||
}
|
||||
|
||||
bool NumberStringBuilder::containsField(Field field) const {
|
||||
for (int32_t i = 0; i < fLength; i++) {
|
||||
if (field == fieldAt(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
@ -106,6 +106,8 @@ class U_I18N_API NumberStringBuilder : public UMemory {
|
||||
|
||||
void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
|
||||
|
||||
bool containsField(Field field) const;
|
||||
|
||||
private:
|
||||
bool fUsingHeap = false;
|
||||
ValueOrHeapArray<char16_t> fChars;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "uassert.h"
|
||||
#include "unicode/platform.h"
|
||||
#include "unicode/uniset.h"
|
||||
#include "standardplural.h"
|
||||
|
||||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
@ -45,6 +46,7 @@ class Modifier;
|
||||
class MutablePatternModifier;
|
||||
class DecimalQuantity;
|
||||
class NumberStringBuilder;
|
||||
class ModifierStore;
|
||||
struct MicroProps;
|
||||
|
||||
|
||||
@ -127,12 +129,13 @@ class U_I18N_API AffixPatternProvider {
|
||||
virtual bool hasBody() const = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
|
||||
* builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
|
||||
* like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
|
||||
*
|
||||
* A Modifier is usually immutable, except in cases such as {@link MurkyModifier}, which are mutable for performance
|
||||
* A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
|
||||
* reasons.
|
||||
*
|
||||
* Exported as U_I18N_API because it is a base class for other exported types
|
||||
@ -162,12 +165,12 @@ class U_I18N_API Modifier {
|
||||
*
|
||||
* @return The number of characters (UTF-16 code units) in the prefix.
|
||||
*/
|
||||
virtual int32_t getPrefixLength(UErrorCode& status) const = 0;
|
||||
virtual int32_t getPrefixLength() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the number of code points in the modifier, prefix plus suffix.
|
||||
*/
|
||||
virtual int32_t getCodePointCount(UErrorCode& status) const = 0;
|
||||
virtual int32_t getCodePointCount() const = 0;
|
||||
|
||||
/**
|
||||
* Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed
|
||||
@ -177,8 +180,57 @@ class U_I18N_API Modifier {
|
||||
* @return Whether the modifier is strong.
|
||||
*/
|
||||
virtual bool isStrong() const = 0;
|
||||
|
||||
/**
|
||||
* Whether the modifier contains at least one occurrence of the given field.
|
||||
*/
|
||||
virtual bool containsField(UNumberFormatFields field) const = 0;
|
||||
|
||||
/**
|
||||
* A fill-in for getParameters(). obj will always be set; if non-null, the other
|
||||
* two fields are also safe to read.
|
||||
*/
|
||||
struct Parameters {
|
||||
const ModifierStore* obj = nullptr;
|
||||
int8_t signum;
|
||||
StandardPlural::Form plural;
|
||||
|
||||
Parameters();
|
||||
Parameters(const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a set of "parameters" for this Modifier.
|
||||
*
|
||||
* TODO: Make this return a `const Parameters*` more like Java?
|
||||
*/
|
||||
virtual void getParameters(Parameters& output) const = 0;
|
||||
|
||||
/**
|
||||
* Returns whether this Modifier is *semantically equivalent* to the other Modifier;
|
||||
* in many cases, this is the same as equal, but parameters should be ignored.
|
||||
*/
|
||||
virtual bool semanticallyEquivalent(const Modifier& other) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This is *not* a modifier; rather, it is an object that can return modifiers
|
||||
* based on given parameters.
|
||||
*
|
||||
* Exported as U_I18N_API because it is a base class for other exported types.
|
||||
*/
|
||||
class U_I18N_API ModifierStore {
|
||||
public:
|
||||
virtual ~ModifierStore();
|
||||
|
||||
/**
|
||||
* Returns a Modifier with the given parameters (best-effort).
|
||||
*/
|
||||
virtual const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This interface is used when all number formatting settings, including the locale, are known, except for the quantity
|
||||
* itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
|
||||
|
@ -56,6 +56,11 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only accept one exponent per string.
|
||||
if (0 != (result.flags & FLAG_HAS_EXPONENT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First match the scientific separator, and then match another number after it.
|
||||
// NOTE: This is guarded by the smoke test; no need to check fExponentSeparatorString length again.
|
||||
int overlap1 = segment.getCommonPrefixLength(fExponentSeparatorString);
|
||||
|
438
icu4c/source/i18n/numrange_fluent.cpp
Normal file
438
icu4c/source/i18n/numrange_fluent.cpp
Normal file
@ -0,0 +1,438 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
// Allow implicit conversion from char16_t* to UnicodeString for this file:
|
||||
// Helpful in toString methods and elsewhere.
|
||||
#define UNISTR_FROM_STRING_EXPLICIT
|
||||
|
||||
#include "numrange_impl.h"
|
||||
#include "util.h"
|
||||
#include "number_utypes.h"
|
||||
|
||||
using namespace icu;
|
||||
using namespace icu::number;
|
||||
using namespace icu::number::impl;
|
||||
|
||||
|
||||
// This function needs to be declared in this namespace so it can be friended.
|
||||
// NOTE: In Java, this logic is handled in the resolve() function.
|
||||
void icu::number::impl::touchRangeLocales(RangeMacroProps& macros) {
|
||||
macros.formatter1.fMacros.locale = macros.locale;
|
||||
macros.formatter2.fMacros.locale = macros.locale;
|
||||
}
|
||||
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const UnlocalizedNumberFormatter& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = formatter;
|
||||
copy.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const UnlocalizedNumberFormatter& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = formatter;
|
||||
move.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNumberFormatter&& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNumberFormatter&& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const UnlocalizedNumberFormatter& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = formatter;
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const UnlocalizedNumberFormatter& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = formatter;
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedNumberFormatter&& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedNumberFormatter&& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const UnlocalizedNumberFormatter& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter2 = formatter;
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const UnlocalizedNumberFormatter& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter2 = formatter;
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(UnlocalizedNumberFormatter&& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter2 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(UnlocalizedNumberFormatter&& formatter) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter2 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.collapse = collapse;
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.collapse = collapse;
|
||||
return move;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.identityFallback = identityFallback;
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) && {
|
||||
Derived move(std::move(*this));
|
||||
move.fMacros.identityFallback = identityFallback;
|
||||
return move;
|
||||
}
|
||||
|
||||
// Declare all classes that implement NumberRangeFormatterSettings
|
||||
// See https://stackoverflow.com/a/495056/1407170
|
||||
template
|
||||
class icu::number::NumberRangeFormatterSettings<icu::number::UnlocalizedNumberRangeFormatter>;
|
||||
template
|
||||
class icu::number::NumberRangeFormatterSettings<icu::number::LocalizedNumberRangeFormatter>;
|
||||
|
||||
|
||||
UnlocalizedNumberRangeFormatter NumberRangeFormatter::with() {
|
||||
UnlocalizedNumberRangeFormatter result;
|
||||
return result;
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter NumberRangeFormatter::withLocale(const Locale& locale) {
|
||||
return with().locale(locale);
|
||||
}
|
||||
|
||||
|
||||
template<typename T> using NFS = NumberRangeFormatterSettings<T>;
|
||||
using LNF = LocalizedNumberRangeFormatter;
|
||||
using UNF = UnlocalizedNumberRangeFormatter;
|
||||
|
||||
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(const UNF& other)
|
||||
: UNF(static_cast<const NFS<UNF>&>(other)) {}
|
||||
|
||||
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(const NFS<UNF>& other)
|
||||
: NFS<UNF>(other) {
|
||||
// No additional fields to assign
|
||||
}
|
||||
|
||||
// Make default copy constructor call the NumberRangeFormatterSettings copy constructor.
|
||||
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(UNF&& src) U_NOEXCEPT
|
||||
: UNF(static_cast<NFS<UNF>&&>(src)) {}
|
||||
|
||||
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(NFS<UNF>&& src) U_NOEXCEPT
|
||||
: NFS<UNF>(std::move(src)) {
|
||||
// No additional fields to assign
|
||||
}
|
||||
|
||||
UnlocalizedNumberRangeFormatter& UnlocalizedNumberRangeFormatter::operator=(const UNF& other) {
|
||||
NFS<UNF>::operator=(static_cast<const NFS<UNF>&>(other));
|
||||
// No additional fields to assign
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnlocalizedNumberRangeFormatter& UnlocalizedNumberRangeFormatter::operator=(UNF&& src) U_NOEXCEPT {
|
||||
NFS<UNF>::operator=(static_cast<NFS<UNF>&&>(src));
|
||||
// No additional fields to assign
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Make default copy constructor call the NumberRangeFormatterSettings copy constructor.
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const LNF& other)
|
||||
: LNF(static_cast<const NFS<LNF>&>(other)) {}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const NFS<LNF>& other)
|
||||
: NFS<LNF>(other) {
|
||||
// No additional fields to assign
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT
|
||||
: LNF(static_cast<NFS<LNF>&&>(src)) {}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(NFS<LNF>&& src) U_NOEXCEPT
|
||||
: NFS<LNF>(std::move(src)) {
|
||||
// Steal the compiled formatter
|
||||
LNF&& _src = static_cast<LNF&&>(src);
|
||||
fImpl = _src.fImpl;
|
||||
_src.fImpl = nullptr;
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(const LNF& other) {
|
||||
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
|
||||
// Do not steal; just clear
|
||||
delete fImpl;
|
||||
fImpl = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(LNF&& src) U_NOEXCEPT {
|
||||
NFS<LNF>::operator=(static_cast<NFS<LNF>&&>(src));
|
||||
// Steal the compiled formatter
|
||||
delete fImpl;
|
||||
fImpl = src.fImpl;
|
||||
src.fImpl = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
LocalizedNumberRangeFormatter::~LocalizedNumberRangeFormatter() {
|
||||
delete fImpl;
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const RangeMacroProps& macros, const Locale& locale) {
|
||||
fMacros = macros;
|
||||
fMacros.locale = locale;
|
||||
touchRangeLocales(fMacros);
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(RangeMacroProps&& macros, const Locale& locale) {
|
||||
fMacros = std::move(macros);
|
||||
fMacros.locale = locale;
|
||||
touchRangeLocales(fMacros);
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Locale& locale) const& {
|
||||
return LocalizedNumberRangeFormatter(fMacros, locale);
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Locale& locale)&& {
|
||||
return LocalizedNumberRangeFormatter(std::move(fMacros), locale);
|
||||
}
|
||||
|
||||
|
||||
FormattedNumberRange LocalizedNumberRangeFormatter::formatFormattableRange(
|
||||
const Formattable& first, const Formattable& second, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return FormattedNumberRange(U_ILLEGAL_ARGUMENT_ERROR);
|
||||
}
|
||||
|
||||
auto results = new UFormattedNumberRangeData();
|
||||
if (results == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return FormattedNumberRange(status);
|
||||
}
|
||||
|
||||
first.populateDecimalQuantity(results->quantity1, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return FormattedNumberRange(status);
|
||||
}
|
||||
|
||||
second.populateDecimalQuantity(results->quantity2, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return FormattedNumberRange(status);
|
||||
}
|
||||
|
||||
formatImpl(*results, first == second, status);
|
||||
|
||||
// Do not save the results object if we encountered a failure.
|
||||
if (U_SUCCESS(status)) {
|
||||
return FormattedNumberRange(results);
|
||||
} else {
|
||||
delete results;
|
||||
return FormattedNumberRange(status);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalizedNumberRangeFormatter::formatImpl(
|
||||
UFormattedNumberRangeData& results, bool equalBeforeRounding, UErrorCode& status) const {
|
||||
if (fImpl == nullptr) {
|
||||
// TODO: Fix this once the atomic is ready!
|
||||
auto* nonConstThis = const_cast<LocalizedNumberRangeFormatter*>(this);
|
||||
nonConstThis->fImpl = new NumberRangeFormatterImpl(fMacros, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
if (fImpl == nullptr) {
|
||||
status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fImpl->format(results, equalBeforeRounding, status);
|
||||
}
|
||||
|
||||
|
||||
FormattedNumberRange::FormattedNumberRange(FormattedNumberRange&& src) U_NOEXCEPT
|
||||
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
}
|
||||
|
||||
FormattedNumberRange& FormattedNumberRange::operator=(FormattedNumberRange&& src) U_NOEXCEPT {
|
||||
delete fResults;
|
||||
fResults = src.fResults;
|
||||
fErrorCode = src.fErrorCode;
|
||||
// Disown src.fResults to prevent double-deletion
|
||||
src.fResults = nullptr;
|
||||
src.fErrorCode = U_INVALID_STATE_ERROR;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UnicodeString FormattedNumberRange::toString(UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
return fResults->string.toUnicodeString();
|
||||
}
|
||||
|
||||
Appendable& FormattedNumberRange::appendTo(Appendable& appendable, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return appendable;
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return appendable;
|
||||
}
|
||||
appendable.appendString(fResults->string.chars(), fResults->string.length());
|
||||
return appendable;
|
||||
}
|
||||
|
||||
UBool FormattedNumberRange::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return FALSE;
|
||||
}
|
||||
// NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
|
||||
return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
void FormattedNumberRange::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const {
|
||||
FieldPositionIteratorHandler fpih(&iterator, status);
|
||||
getAllFieldPositionsImpl(fpih, status);
|
||||
}
|
||||
|
||||
void FormattedNumberRange::getAllFieldPositionsImpl(
|
||||
FieldPositionIteratorHandler& fpih, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return;
|
||||
}
|
||||
fResults->string.getAllFieldPositions(fpih, status);
|
||||
}
|
||||
|
||||
UnicodeString FormattedNumberRange::getFirstDecimal(UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
return fResults->quantity1.toScientificString();
|
||||
}
|
||||
|
||||
UnicodeString FormattedNumberRange::getSecondDecimal(UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return ICU_Utility::makeBogusString();
|
||||
}
|
||||
return fResults->quantity2.toScientificString();
|
||||
}
|
||||
|
||||
UNumberRangeIdentityResult FormattedNumberRange::getIdentityResult(UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return UNUM_IDENTITY_RESULT_NOT_EQUAL;
|
||||
}
|
||||
if (fResults == nullptr) {
|
||||
status = fErrorCode;
|
||||
return UNUM_IDENTITY_RESULT_NOT_EQUAL;
|
||||
}
|
||||
return fResults->identityResult;
|
||||
}
|
||||
|
||||
FormattedNumberRange::~FormattedNumberRange() {
|
||||
delete fResults;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
483
icu4c/source/i18n/numrange_impl.cpp
Normal file
483
icu4c/source/i18n/numrange_impl.cpp
Normal file
@ -0,0 +1,483 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
// Allow implicit conversion from char16_t* to UnicodeString for this file:
|
||||
// Helpful in toString methods and elsewhere.
|
||||
#define UNISTR_FROM_STRING_EXPLICIT
|
||||
|
||||
#include "unicode/numberrangeformatter.h"
|
||||
#include "numrange_impl.h"
|
||||
#include "patternprops.h"
|
||||
#include "uresimp.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace icu;
|
||||
using namespace icu::number;
|
||||
using namespace icu::number::impl;
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function for 2-dimensional switch statement
|
||||
constexpr int8_t identity2d(UNumberRangeIdentityFallback a, UNumberRangeIdentityResult b) {
|
||||
return static_cast<int8_t>(a) | (static_cast<int8_t>(b) << 4);
|
||||
}
|
||||
|
||||
|
||||
struct NumberRangeData {
|
||||
SimpleFormatter rangePattern;
|
||||
SimpleFormatter approximatelyPattern;
|
||||
};
|
||||
|
||||
class NumberRangeDataSink : public ResourceSink {
|
||||
public:
|
||||
NumberRangeDataSink(NumberRangeData& data) : fData(data) {}
|
||||
|
||||
void put(const char* key, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
|
||||
ResourceTable miscTable = value.getTable(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int i = 0; miscTable.getKeyAndValue(i, key, value); i++) {
|
||||
if (uprv_strcmp(key, "range") == 0) {
|
||||
if (fData.rangePattern.getArgumentLimit() != 0) {
|
||||
continue; // have already seen this pattern
|
||||
}
|
||||
fData.rangePattern = {value.getUnicodeString(status), status};
|
||||
} else if (uprv_strcmp(key, "approximately") == 0) {
|
||||
if (fData.approximatelyPattern.getArgumentLimit() != 0) {
|
||||
continue; // have already seen this pattern
|
||||
}
|
||||
fData.approximatelyPattern = {value.getUnicodeString(status), status};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NumberRangeData& fData;
|
||||
};
|
||||
|
||||
void getNumberRangeData(const char* localeName, const char* nsName, NumberRangeData& data, UErrorCode& status) {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
LocalUResourceBundlePointer rb(ures_open(NULL, localeName, &status));
|
||||
if (U_FAILURE(status)) { return; }
|
||||
NumberRangeDataSink sink(data);
|
||||
|
||||
CharString dataPath;
|
||||
dataPath.append("NumberElements/", -1, status);
|
||||
dataPath.append(nsName, -1, status);
|
||||
dataPath.append("/miscPatterns", -1, status);
|
||||
ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
|
||||
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
|
||||
|
||||
if (data.rangePattern.getArgumentLimit() == 0) {
|
||||
// No data!
|
||||
data.rangePattern = {u"{0}–{1}", status};
|
||||
}
|
||||
if (data.approximatelyPattern.getArgumentLimit() == 0) {
|
||||
// No data!
|
||||
data.approximatelyPattern = {u"~{0}", status};
|
||||
}
|
||||
}
|
||||
|
||||
class PluralRangesDataSink : public ResourceSink {
|
||||
public:
|
||||
PluralRangesDataSink(StandardPluralRanges& output) : fOutput(output) {}
|
||||
|
||||
void put(const char* /*key*/, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
|
||||
ResourceArray entriesArray = value.getArray(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
fOutput.setCapacity(entriesArray.getSize());
|
||||
for (int i = 0; entriesArray.getValue(i, value); i++) {
|
||||
ResourceArray pluralFormsArray = value.getArray(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
pluralFormsArray.getValue(0, value);
|
||||
StandardPlural::Form first = StandardPlural::fromString(value.getUnicodeString(status), status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
pluralFormsArray.getValue(1, value);
|
||||
StandardPlural::Form second = StandardPlural::fromString(value.getUnicodeString(status), status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
pluralFormsArray.getValue(2, value);
|
||||
StandardPlural::Form result = StandardPlural::fromString(value.getUnicodeString(status), status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
fOutput.addPluralRange(first, second, result);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
StandardPluralRanges& fOutput;
|
||||
};
|
||||
|
||||
void getPluralRangesData(const Locale& locale, StandardPluralRanges& output, UErrorCode& status) {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "pluralRanges", &status));
|
||||
if (U_FAILURE(status)) { return; }
|
||||
|
||||
CharString dataPath;
|
||||
dataPath.append("locales/", -1, status);
|
||||
dataPath.append(locale.getLanguage(), -1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
int32_t setLen;
|
||||
// Not all languages are covered: fail gracefully
|
||||
UErrorCode internalStatus = U_ZERO_ERROR;
|
||||
const UChar* set = ures_getStringByKeyWithFallback(rb.getAlias(), dataPath.data(), &setLen, &internalStatus);
|
||||
if (U_FAILURE(internalStatus)) { return; }
|
||||
|
||||
dataPath.clear();
|
||||
dataPath.append("rules/", -1, status);
|
||||
dataPath.appendInvariantChars(set, setLen, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
PluralRangesDataSink sink(output);
|
||||
ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
void StandardPluralRanges::initialize(const Locale& locale, UErrorCode& status) {
|
||||
getPluralRangesData(locale, *this, status);
|
||||
}
|
||||
|
||||
void StandardPluralRanges::addPluralRange(
|
||||
StandardPlural::Form first,
|
||||
StandardPlural::Form second,
|
||||
StandardPlural::Form result) {
|
||||
U_ASSERT(fTriplesLen < fTriples.getCapacity());
|
||||
fTriples[fTriplesLen] = {first, second, result};
|
||||
fTriplesLen++;
|
||||
}
|
||||
|
||||
void StandardPluralRanges::setCapacity(int32_t length) {
|
||||
if (length > fTriples.getCapacity()) {
|
||||
fTriples.resize(length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
StandardPlural::Form
|
||||
StandardPluralRanges::resolve(StandardPlural::Form first, StandardPlural::Form second) const {
|
||||
for (int32_t i=0; i<fTriplesLen; i++) {
|
||||
const auto& triple = fTriples[i];
|
||||
if (triple.first == first && triple.second == second) {
|
||||
return triple.result;
|
||||
}
|
||||
}
|
||||
// Default fallback
|
||||
return StandardPlural::OTHER;
|
||||
}
|
||||
|
||||
|
||||
NumberRangeFormatterImpl::NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status)
|
||||
: formatterImpl1(macros.formatter1.fMacros, status),
|
||||
formatterImpl2(macros.formatter2.fMacros, status),
|
||||
fSameFormatters(macros.singleFormatter),
|
||||
fCollapse(macros.collapse),
|
||||
fIdentityFallback(macros.identityFallback) {
|
||||
|
||||
// TODO: As of this writing (ICU 63), there is no locale that has different number miscPatterns
|
||||
// based on numbering system. Therefore, data is loaded only from latn. If this changes,
|
||||
// this part of the code should be updated to load from the local numbering system.
|
||||
// The numbering system could come from the one specified in the NumberFormatter passed to
|
||||
// numberFormatterBoth() or similar.
|
||||
// See ICU-20144
|
||||
|
||||
NumberRangeData data;
|
||||
getNumberRangeData(macros.locale.getName(), "latn", data, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
fRangeFormatter = data.rangePattern;
|
||||
fApproximatelyModifier = {data.approximatelyPattern, UNUM_FIELD_COUNT, false};
|
||||
|
||||
// TODO: Get locale from PluralRules instead?
|
||||
fPluralRanges.initialize(macros.locale, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
}
|
||||
|
||||
void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MicroProps micros1;
|
||||
MicroProps micros2;
|
||||
formatterImpl1.preProcess(data.quantity1, micros1, status);
|
||||
if (fSameFormatters) {
|
||||
formatterImpl1.preProcess(data.quantity2, micros2, status);
|
||||
} else {
|
||||
formatterImpl2.preProcess(data.quantity2, micros2, status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If any of the affixes are different, an identity is not possible
|
||||
// and we must use formatRange().
|
||||
// TODO: Write this as MicroProps operator==() ?
|
||||
// TODO: Avoid the redundancy of these equality operations with the
|
||||
// ones in formatRange?
|
||||
if (!micros1.modInner->semanticallyEquivalent(*micros2.modInner)
|
||||
|| !micros1.modMiddle->semanticallyEquivalent(*micros2.modMiddle)
|
||||
|| !micros1.modOuter->semanticallyEquivalent(*micros2.modOuter)) {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for identity
|
||||
if (equalBeforeRounding) {
|
||||
data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
|
||||
} else if (data.quantity1 == data.quantity2) {
|
||||
data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING;
|
||||
} else {
|
||||
data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
|
||||
}
|
||||
|
||||
switch (identity2d(fIdentityFallback, data.identityResult)) {
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
|
||||
UNUM_IDENTITY_RESULT_NOT_EQUAL):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
|
||||
UNUM_IDENTITY_RESULT_NOT_EQUAL):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_NOT_EQUAL):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_NOT_EQUAL):
|
||||
formatRange(data, micros1, micros2, status);
|
||||
break;
|
||||
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
|
||||
formatApproximately(data, micros1, micros2, status);
|
||||
break;
|
||||
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
|
||||
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
|
||||
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
|
||||
formatSingleValue(data, micros1, micros2, status);
|
||||
break;
|
||||
|
||||
default:
|
||||
U_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (fSameFormatters) {
|
||||
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
|
||||
} else {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (fSameFormatters) {
|
||||
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
length += NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
|
||||
fApproximatelyModifier.apply(data.string, 0, length, status);
|
||||
} else {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
|
||||
// modInner is always notation (scientific); collapsable in ALL.
|
||||
// modOuter is always units; collapsable in ALL, AUTO, and UNIT.
|
||||
// modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
|
||||
// Never collapse an outer mod but not an inner mod.
|
||||
bool collapseOuter, collapseMiddle, collapseInner;
|
||||
switch (fCollapse) {
|
||||
case UNUM_RANGE_COLLAPSE_ALL:
|
||||
case UNUM_RANGE_COLLAPSE_AUTO:
|
||||
case UNUM_RANGE_COLLAPSE_UNIT:
|
||||
{
|
||||
// OUTER MODIFIER
|
||||
collapseOuter = micros1.modOuter->semanticallyEquivalent(*micros2.modOuter);
|
||||
|
||||
if (!collapseOuter) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER
|
||||
collapseMiddle = micros1.modMiddle->semanticallyEquivalent(*micros2.modMiddle);
|
||||
|
||||
if (!collapseMiddle) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER HEURISTICS
|
||||
// (could disable collapsing of the middle modifier)
|
||||
// The modifiers are equal by this point, so we can look at just one of them.
|
||||
const Modifier* mm = micros1.modMiddle;
|
||||
if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
|
||||
// Only collapse if the modifier is a unit.
|
||||
// TODO: Make a better way to check for a unit?
|
||||
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
|
||||
if (!mm->containsField(UNUM_CURRENCY_FIELD) && !mm->containsField(UNUM_PERCENT_FIELD)) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
} else if (fCollapse == UNUM_RANGE_COLLAPSE_AUTO) {
|
||||
// Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
|
||||
if (mm->getCodePointCount() <= 1) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collapseMiddle || fCollapse != UNUM_RANGE_COLLAPSE_ALL) {
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// INNER MODIFIER
|
||||
collapseInner = micros1.modInner->semanticallyEquivalent(*micros2.modInner);
|
||||
|
||||
// All done checking for collapsability.
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
collapseOuter = false;
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
NumberStringBuilder& string = data.string;
|
||||
int32_t lengthPrefix = 0;
|
||||
int32_t length1 = 0;
|
||||
int32_t lengthInfix = 0;
|
||||
int32_t length2 = 0;
|
||||
int32_t lengthSuffix = 0;
|
||||
|
||||
// Use #define so that these are evaluated at the call site.
|
||||
#define UPRV_INDEX_0 (lengthPrefix)
|
||||
#define UPRV_INDEX_1 (lengthPrefix + length1)
|
||||
#define UPRV_INDEX_2 (lengthPrefix + length1 + lengthInfix)
|
||||
#define UPRV_INDEX_3 (lengthPrefix + length1 + lengthInfix + length2)
|
||||
|
||||
int32_t lengthRange = SimpleModifier::formatTwoArgPattern(
|
||||
fRangeFormatter,
|
||||
string,
|
||||
0,
|
||||
&lengthPrefix,
|
||||
&lengthSuffix,
|
||||
UNUM_FIELD_COUNT,
|
||||
status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
lengthInfix = lengthRange - lengthPrefix - lengthSuffix;
|
||||
U_ASSERT(lengthInfix > 0);
|
||||
|
||||
// SPACING HEURISTIC
|
||||
// Add spacing unless all modifiers are collapsed.
|
||||
// TODO: add API to control this?
|
||||
// TODO: Use a data-driven heuristic like currency spacing?
|
||||
// TODO: Use Unicode [:whitespace:] instead of PatternProps whitespace? (consider speed implications)
|
||||
{
|
||||
bool repeatInner = !collapseInner && micros1.modInner->getCodePointCount() > 0;
|
||||
bool repeatMiddle = !collapseMiddle && micros1.modMiddle->getCodePointCount() > 0;
|
||||
bool repeatOuter = !collapseOuter && micros1.modOuter->getCodePointCount() > 0;
|
||||
if (repeatInner || repeatMiddle || repeatOuter) {
|
||||
// Add spacing if there is not already spacing
|
||||
if (!PatternProps::isWhiteSpace(string.charAt(UPRV_INDEX_1))) {
|
||||
lengthInfix += string.insertCodePoint(UPRV_INDEX_1, u'\u0020', UNUM_FIELD_COUNT, status);
|
||||
}
|
||||
if (!PatternProps::isWhiteSpace(string.charAt(UPRV_INDEX_2 - 1))) {
|
||||
lengthInfix += string.insertCodePoint(UPRV_INDEX_2, u'\u0020', UNUM_FIELD_COUNT, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
length1 += NumberFormatterImpl::writeNumber(micros1, data.quantity1, string, UPRV_INDEX_0, status);
|
||||
length2 += NumberFormatterImpl::writeNumber(micros2, data.quantity2, string, UPRV_INDEX_2, status);
|
||||
|
||||
// TODO: Support padding?
|
||||
|
||||
if (collapseInner) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
const Modifier& mod = resolveModifierPlurals(*micros1.modInner, *micros2.modInner);
|
||||
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
length2 += micros2.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
|
||||
}
|
||||
|
||||
if (collapseMiddle) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
const Modifier& mod = resolveModifierPlurals(*micros1.modMiddle, *micros2.modMiddle);
|
||||
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
length2 += micros2.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
|
||||
}
|
||||
|
||||
if (collapseOuter) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
const Modifier& mod = resolveModifierPlurals(*micros1.modOuter, *micros2.modOuter);
|
||||
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
length2 += micros2.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const Modifier&
|
||||
NumberRangeFormatterImpl::resolveModifierPlurals(const Modifier& first, const Modifier& second) const {
|
||||
Modifier::Parameters parameters;
|
||||
first.getParameters(parameters);
|
||||
if (parameters.obj == nullptr) {
|
||||
// No plural form; return a fallback (e.g., the first)
|
||||
return first;
|
||||
}
|
||||
StandardPlural::Form firstPlural = parameters.plural;
|
||||
|
||||
second.getParameters(parameters);
|
||||
if (parameters.obj == nullptr) {
|
||||
// No plural form; return a fallback (e.g., the first)
|
||||
return first;
|
||||
}
|
||||
StandardPlural::Form secondPlural = parameters.plural;
|
||||
|
||||
// Get the required plural form from data
|
||||
StandardPlural::Form resultPlural = fPluralRanges.resolve(firstPlural, secondPlural);
|
||||
|
||||
// Get and return the new Modifier
|
||||
const Modifier* mod = parameters.obj->getModifier(parameters.signum, resultPlural);
|
||||
U_ASSERT(mod != nullptr);
|
||||
return *mod;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
114
icu4c/source/i18n/numrange_impl.h
Normal file
114
icu4c/source/i18n/numrange_impl.h
Normal file
@ -0,0 +1,114 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
#ifndef __SOURCE_NUMRANGE_TYPES_H__
|
||||
#define __SOURCE_NUMRANGE_TYPES_H__
|
||||
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/numberrangeformatter.h"
|
||||
#include "unicode/simpleformatter.h"
|
||||
#include "number_types.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "number_formatimpl.h"
|
||||
#include "number_stringbuilder.h"
|
||||
|
||||
U_NAMESPACE_BEGIN namespace number {
|
||||
namespace impl {
|
||||
|
||||
|
||||
/**
|
||||
* Class similar to UFormattedNumberData.
|
||||
*
|
||||
* Has incomplete magic number logic that will need to be finished
|
||||
* if this is to be exposed as C API in the future.
|
||||
*/
|
||||
struct UFormattedNumberRangeData : public UMemory {
|
||||
// The magic number to identify incoming objects.
|
||||
// Reads in ASCII as "FDR" (FormatteDnumberRange with room at the end)
|
||||
static constexpr int32_t kMagic = 0x46445200;
|
||||
|
||||
// Data members:
|
||||
int32_t fMagic = kMagic;
|
||||
DecimalQuantity quantity1;
|
||||
DecimalQuantity quantity2;
|
||||
NumberStringBuilder string;
|
||||
UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_COUNT;
|
||||
|
||||
// No C conversion methods (no C API yet)
|
||||
};
|
||||
|
||||
|
||||
class StandardPluralRanges : public UMemory {
|
||||
public:
|
||||
void initialize(const Locale& locale, UErrorCode& status);
|
||||
StandardPlural::Form resolve(StandardPlural::Form first, StandardPlural::Form second) const;
|
||||
|
||||
/** Used for data loading. */
|
||||
void addPluralRange(
|
||||
StandardPlural::Form first,
|
||||
StandardPlural::Form second,
|
||||
StandardPlural::Form result);
|
||||
|
||||
/** Used for data loading. */
|
||||
void setCapacity(int32_t length);
|
||||
|
||||
private:
|
||||
struct StandardPluralRangeTriple {
|
||||
StandardPlural::Form first;
|
||||
StandardPlural::Form second;
|
||||
StandardPlural::Form result;
|
||||
};
|
||||
|
||||
// TODO: An array is simple here, but it results in linear lookup time.
|
||||
// Certain locales have 20-30 entries in this list.
|
||||
// Consider changing to a smarter data structure.
|
||||
typedef MaybeStackArray<StandardPluralRangeTriple, 3> PluralRangeTriples;
|
||||
PluralRangeTriples fTriples;
|
||||
int32_t fTriplesLen = 0;
|
||||
};
|
||||
|
||||
|
||||
class NumberRangeFormatterImpl : public UMemory {
|
||||
public:
|
||||
NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status);
|
||||
|
||||
void format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const;
|
||||
|
||||
private:
|
||||
NumberFormatterImpl formatterImpl1;
|
||||
NumberFormatterImpl formatterImpl2;
|
||||
bool fSameFormatters;
|
||||
|
||||
UNumberRangeCollapse fCollapse;
|
||||
UNumberRangeIdentityFallback fIdentityFallback;
|
||||
|
||||
SimpleFormatter fRangeFormatter;
|
||||
SimpleModifier fApproximatelyModifier;
|
||||
|
||||
StandardPluralRanges fPluralRanges;
|
||||
|
||||
void formatSingleValue(UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const;
|
||||
|
||||
void formatApproximately(UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const;
|
||||
|
||||
void formatRange(UFormattedNumberRangeData& data,
|
||||
MicroProps& micros1, MicroProps& micros2,
|
||||
UErrorCode& status) const;
|
||||
|
||||
const Modifier& resolveModifierPlurals(const Modifier& first, const Modifier& second) const;
|
||||
};
|
||||
|
||||
|
||||
} // namespace impl
|
||||
} // namespace number
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif //__SOURCE_NUMRANGE_TYPES_H__
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
@ -130,7 +130,7 @@ class BasicTimeZone;
|
||||
*
|
||||
* **Note:** for some non-Gregorian calendars, different
|
||||
* fields may be necessary for complete disambiguation. For example, a full
|
||||
* specification of the historial Arabic astronomical calendar requires year,
|
||||
* specification of the historical Arabic astronomical calendar requires year,
|
||||
* month, day-of-month *and* day-of-week in some cases.
|
||||
*
|
||||
* **Note:** There are certain possible ambiguities in
|
||||
@ -886,7 +886,7 @@ public:
|
||||
/**
|
||||
* Sets the behavior for handling wall time repeating multiple times
|
||||
* at negative time zone offset transitions. For example, 1:30 AM on
|
||||
* November 6, 2011 in US Eastern time (Ameirca/New_York) occurs twice;
|
||||
* November 6, 2011 in US Eastern time (America/New_York) occurs twice;
|
||||
* 1:30 AM EDT, then 1:30 AM EST one hour later. When <code>UCAL_WALLTIME_FIRST</code>
|
||||
* is used, the wall time 1:30AM in this example will be interpreted as 1:30 AM EDT
|
||||
* (first occurrence). When <code>UCAL_WALLTIME_LAST</code> is used, it will be
|
||||
@ -2152,7 +2152,7 @@ private:
|
||||
TimeZone* fZone;
|
||||
|
||||
/**
|
||||
* Option for rpeated wall time
|
||||
* Option for repeated wall time
|
||||
* @see #setRepeatedWallTimeOption
|
||||
*/
|
||||
UCalendarWallTimeOption fRepeatedWallTime;
|
||||
@ -2437,7 +2437,7 @@ private:
|
||||
BasicTimeZone* getBasicTimeZone() const;
|
||||
|
||||
/**
|
||||
* Find the previous zone transtion near the given time.
|
||||
* Find the previous zone transition near the given time.
|
||||
* @param base The base time, inclusive
|
||||
* @param transitionTime Receives the result time
|
||||
* @param status The error status
|
||||
|
@ -144,6 +144,9 @@ class MultiplierFormatHandler;
|
||||
class CurrencySymbols;
|
||||
class GeneratorHelpers;
|
||||
class DecNum;
|
||||
class NumberRangeFormatterImpl;
|
||||
struct RangeMacroProps;
|
||||
void touchRangeLocales(impl::RangeMacroProps& macros);
|
||||
|
||||
} // namespace impl
|
||||
|
||||
@ -1423,7 +1426,8 @@ struct U_I18N_API MacroProps : public UMemory {
|
||||
|
||||
/**
|
||||
* An abstract base class for specifying settings related to number formatting. This class is implemented by
|
||||
* {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}.
|
||||
* {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended for
|
||||
* public subclassing.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class U_I18N_API NumberFormatterSettings {
|
||||
@ -2108,6 +2112,10 @@ class U_I18N_API NumberFormatterSettings {
|
||||
|
||||
friend class LocalizedNumberFormatter;
|
||||
friend class UnlocalizedNumberFormatter;
|
||||
|
||||
// Give NumberRangeFormatter access to the MacroProps
|
||||
friend void impl::touchRangeLocales(impl::RangeMacroProps& macros);
|
||||
friend class impl::NumberRangeFormatterImpl;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2124,13 +2132,6 @@ class U_I18N_API UnlocalizedNumberFormatter
|
||||
* Associate the given locale with the number formatter. The locale is used for picking the appropriate symbols,
|
||||
* formats, and other data for number display.
|
||||
*
|
||||
* <p>
|
||||
* To use the Java default locale, call Locale::getDefault():
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter::with(). ... .locale(Locale::getDefault())
|
||||
* </pre>
|
||||
*
|
||||
* @param locale
|
||||
* The locale to use when loading data for number formatting.
|
||||
* @return The fluent chain.
|
||||
@ -2156,7 +2157,6 @@ class U_I18N_API UnlocalizedNumberFormatter
|
||||
*/
|
||||
UnlocalizedNumberFormatter() = default;
|
||||
|
||||
// Make default copy constructor call the NumberFormatterSettings copy constructor.
|
||||
/**
|
||||
* Returns a copy of this UnlocalizedNumberFormatter.
|
||||
* @draft ICU 60
|
||||
@ -2295,7 +2295,6 @@ class U_I18N_API LocalizedNumberFormatter
|
||||
*/
|
||||
LocalizedNumberFormatter() = default;
|
||||
|
||||
// Make default copy constructor call the NumberFormatterSettings copy constructor.
|
||||
/**
|
||||
* Returns a copy of this LocalizedNumberFormatter.
|
||||
* @draft ICU 60
|
||||
@ -2359,6 +2358,8 @@ class U_I18N_API LocalizedNumberFormatter
|
||||
|
||||
LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale);
|
||||
|
||||
void clear();
|
||||
|
||||
void lnfMoveHelper(LocalizedNumberFormatter&& src);
|
||||
|
||||
/**
|
||||
@ -2457,9 +2458,9 @@ class U_I18N_API FormattedNumber : public UMemory {
|
||||
#endif /* U_HIDE_DEPRECATED_API */
|
||||
|
||||
/**
|
||||
* Determines the start and end indices of the next occurrence of the given <em>field</em> in the
|
||||
* output string. This allows you to determine the locations of, for example, the integer part,
|
||||
* fraction part, or symbols.
|
||||
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
|
||||
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
|
||||
* the integer part, fraction part, or symbols.
|
||||
*
|
||||
* If a field occurs just once, calling this method will find that occurrence and return it. If a
|
||||
* field occurs multiple times, this method may be called repeatedly with the following pattern:
|
||||
@ -2478,7 +2479,7 @@ class U_I18N_API FormattedNumber : public UMemory {
|
||||
* Input+output variable. On input, the "field" property determines which field to look
|
||||
* up, and the "beginIndex" and "endIndex" properties determine where to begin the search.
|
||||
* On output, the "beginIndex" is set to the beginning of the first occurrence of the
|
||||
* field with either begin or end indices after the input indices, "endIndex" is set to
|
||||
* field with either begin or end indices after the input indices; "endIndex" is set to
|
||||
* the end of that occurrence of the field (exclusive index). If a field position is not
|
||||
* found, the method returns FALSE and the FieldPosition may or may not be changed.
|
||||
* @param status
|
||||
|
851
icu4c/source/i18n/unicode/numberrangeformatter.h
Normal file
851
icu4c/source/i18n/unicode/numberrangeformatter.h
Normal file
@ -0,0 +1,851 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
#ifndef __NUMBERRANGEFORMATTER_H__
|
||||
#define __NUMBERRANGEFORMATTER_H__
|
||||
|
||||
#include "unicode/appendable.h"
|
||||
#include "unicode/fieldpos.h"
|
||||
#include "unicode/fpositer.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief C++ API: Library for localized formatting of number, currency, and unit ranges.
|
||||
*
|
||||
* The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
|
||||
* <p>
|
||||
* Usage example:
|
||||
* <p>
|
||||
* <pre>
|
||||
* NumberRangeFormatter::with()
|
||||
* .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
|
||||
* .numberFormatterFirst(NumberFormatter::with().adoptUnit(MeasureUnit::createMeter()))
|
||||
* .numberFormatterSecond(NumberFormatter::with().adoptUnit(MeasureUnit::createKilometer()))
|
||||
* .locale("en-GB")
|
||||
* .formatRange(750, 1.2, status)
|
||||
* .toString(status);
|
||||
* // => "750 m - 1.2 km"
|
||||
* </pre>
|
||||
* <p>
|
||||
* Like NumberFormatter, NumberRangeFormatter instances are immutable and thread-safe. This API is based on the
|
||||
* <em>fluent</em> design pattern popularized by libraries such as Google's Guava.
|
||||
*
|
||||
* @author Shane Carr
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Defines how to merge fields that are identical across the range sign.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
typedef enum UNumberRangeCollapse {
|
||||
/**
|
||||
* Use locale data and heuristics to determine how much of the string to collapse. Could end up collapsing none,
|
||||
* some, or all repeated pieces in a locale-sensitive way.
|
||||
*
|
||||
* The heuristics used for this option are subject to change over time.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_RANGE_COLLAPSE_AUTO,
|
||||
|
||||
/**
|
||||
* Do not collapse any part of the number. Example: "3.2 thousand kilograms – 5.3 thousand kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_RANGE_COLLAPSE_NONE,
|
||||
|
||||
/**
|
||||
* Collapse the unit part of the number, but not the notation, if present. Example: "3.2 thousand – 5.3 thousand
|
||||
* kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_RANGE_COLLAPSE_UNIT,
|
||||
|
||||
/**
|
||||
* Collapse any field that is equal across the range sign. May introduce ambiguity on the magnitude of the
|
||||
* number. Example: "3.2 – 5.3 thousand kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_RANGE_COLLAPSE_ALL
|
||||
} UNumberRangeCollapse;
|
||||
|
||||
/**
|
||||
* Defines the behavior when the two numbers in the range are identical after rounding. To programmatically detect
|
||||
* when the identity fallback is used, compare the lower and upper BigDecimals via FormattedNumber.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
typedef enum UNumberRangeIdentityFallback {
|
||||
/**
|
||||
* Show the number as a single value rather than a range. Example: "$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
|
||||
|
||||
/**
|
||||
* Show the number using a locale-sensitive approximation pattern. If the numbers were the same before rounding,
|
||||
* show the single value. Example: "~$5" or "$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
|
||||
|
||||
/**
|
||||
* Show the number using a locale-sensitive approximation pattern. Use the range pattern always, even if the
|
||||
* inputs are the same. Example: "~$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
|
||||
|
||||
/**
|
||||
* Show the number as the range of two equal values. Use the range pattern always, even if the inputs are the
|
||||
* same. Example (with RangeCollapse.NONE): "$5 – $5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UNUM_IDENTITY_FALLBACK_RANGE
|
||||
} UNumberRangeIdentityFallback;
|
||||
|
||||
/**
|
||||
* Used in the result class FormattedNumberRange to indicate to the user whether the numbers formatted in the range
|
||||
* were equal or not, and whether or not the identity fallback was applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
typedef enum UNumberRangeIdentityResult {
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were equal, even before any rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING,
|
||||
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were equal, but only after rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING,
|
||||
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were not equal, even after rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
UNUM_IDENTITY_RESULT_NOT_EQUAL,
|
||||
|
||||
#ifndef U_HIDE_INTERNAL_API
|
||||
/**
|
||||
* The number of entries in this enum.
|
||||
* @internal
|
||||
*/
|
||||
UNUM_IDENTITY_RESULT_COUNT
|
||||
#endif
|
||||
|
||||
} UNumberRangeIdentityResult;
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
namespace number { // icu::number
|
||||
|
||||
// Forward declarations:
|
||||
class UnlocalizedNumberRangeFormatter;
|
||||
class LocalizedNumberRangeFormatter;
|
||||
class FormattedNumberRange;
|
||||
|
||||
namespace impl {
|
||||
|
||||
// Forward declarations:
|
||||
struct RangeMacroProps;
|
||||
class DecimalQuantity;
|
||||
struct UFormattedNumberRangeData;
|
||||
class NumberRangeFormatterImpl;
|
||||
|
||||
} // namespace impl
|
||||
|
||||
// Other helper classes would go here, but there are none.
|
||||
|
||||
namespace impl { // icu::number::impl
|
||||
|
||||
// Do not enclose entire MacroProps with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
|
||||
/** @internal */
|
||||
struct U_I18N_API RangeMacroProps : public UMemory {
|
||||
/** @internal */
|
||||
UnlocalizedNumberFormatter formatter1; // = NumberFormatter::with();
|
||||
|
||||
/** @internal */
|
||||
UnlocalizedNumberFormatter formatter2; // = NumberFormatter::with();
|
||||
|
||||
/** @internal */
|
||||
bool singleFormatter = true;
|
||||
|
||||
/** @internal */
|
||||
UNumberRangeCollapse collapse = UNUM_RANGE_COLLAPSE_AUTO;
|
||||
|
||||
/** @internal */
|
||||
UNumberRangeIdentityFallback identityFallback = UNUM_IDENTITY_FALLBACK_APPROXIMATELY;
|
||||
|
||||
/** @internal */
|
||||
Locale locale;
|
||||
|
||||
// NOTE: Uses default copy and move constructors.
|
||||
|
||||
/**
|
||||
* Check all members for errors.
|
||||
* @internal
|
||||
*/
|
||||
bool copyErrorTo(UErrorCode &status) const {
|
||||
return formatter1.copyErrorTo(status) || formatter2.copyErrorTo(status);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
/**
|
||||
* An abstract base class for specifying settings related to number formatting. This class is implemented by
|
||||
* {@link UnlocalizedNumberRangeFormatter} and {@link LocalizedNumberRangeFormatter}. This class is not intended for
|
||||
* public subclassing.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class U_I18N_API NumberRangeFormatterSettings {
|
||||
public:
|
||||
/**
|
||||
* Sets the NumberFormatter instance to use for the numbers in the range. The same formatter is applied to both
|
||||
* sides of the range.
|
||||
* <p>
|
||||
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatter
|
||||
* The formatter to use for both numbers in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterBoth(const UnlocalizedNumberFormatter &formatter) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterBoth() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatter
|
||||
* The formatter to use for both numbers in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterBoth
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterBoth(const UnlocalizedNumberFormatter &formatter) &&;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterBoth() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatter
|
||||
* The formatter to use for both numbers in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterBoth
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterBoth(UnlocalizedNumberFormatter &&formatter) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterBoth() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatter
|
||||
* The formatter to use for both numbers in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterBoth
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterBoth(UnlocalizedNumberFormatter &&formatter) &&;
|
||||
|
||||
/**
|
||||
* Sets the NumberFormatter instance to use for the first number in the range.
|
||||
* <p>
|
||||
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatterFirst
|
||||
* The formatter to use for the first number in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterFirst(const UnlocalizedNumberFormatter &formatterFirst) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterFirst() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterFirst
|
||||
* The formatter to use for the first number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterFirst
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterFirst(const UnlocalizedNumberFormatter &formatterFirst) &&;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterFirst() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterFirst
|
||||
* The formatter to use for the first number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterFirst
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterFirst(UnlocalizedNumberFormatter &&formatterFirst) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterFirst() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterFirst
|
||||
* The formatter to use for the first number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterFirst
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterFirst(UnlocalizedNumberFormatter &&formatterFirst) &&;
|
||||
|
||||
/**
|
||||
* Sets the NumberFormatter instance to use for the second number in the range.
|
||||
* <p>
|
||||
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatterSecond
|
||||
* The formatter to use for the second number in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterSecond(const UnlocalizedNumberFormatter &formatterSecond) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterSecond() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterSecond
|
||||
* The formatter to use for the second number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterSecond
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterSecond(const UnlocalizedNumberFormatter &formatterSecond) &&;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterSecond() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterSecond
|
||||
* The formatter to use for the second number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterSecond
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterSecond(UnlocalizedNumberFormatter &&formatterSecond) const &;
|
||||
|
||||
/**
|
||||
* Overload of numberFormatterSecond() for use on an rvalue reference.
|
||||
*
|
||||
* @param formatterSecond
|
||||
* The formatter to use for the second number in the range.
|
||||
* @return The fluent chain.
|
||||
* @see #numberFormatterSecond
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived numberFormatterSecond(UnlocalizedNumberFormatter &&formatterSecond) &&;
|
||||
|
||||
/**
|
||||
* Sets the aggressiveness of "collapsing" fields across the range separator. Possible values:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>ALL: "3-5K miles"</li>
|
||||
* <li>UNIT: "3K - 5K miles"</li>
|
||||
* <li>NONE: "3K miles - 5K miles"</li>
|
||||
* <li>AUTO: usually UNIT or NONE, depending on the locale and formatter settings</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The default value is AUTO.
|
||||
*
|
||||
* @param collapse
|
||||
* The collapsing strategy to use for this range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived collapse(UNumberRangeCollapse collapse) const &;
|
||||
|
||||
/**
|
||||
* Overload of collapse() for use on an rvalue reference.
|
||||
*
|
||||
* @param collapse
|
||||
* The collapsing strategy to use for this range.
|
||||
* @return The fluent chain.
|
||||
* @see #collapse
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived collapse(UNumberRangeCollapse collapse) &&;
|
||||
|
||||
/**
|
||||
* Sets the behavior when the two sides of the range are the same. This could happen if the same two numbers are
|
||||
* passed to the formatRange function, or if different numbers are passed to the function but they become the same
|
||||
* after rounding rules are applied. Possible values:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>SINGLE_VALUE: "5 miles"</li>
|
||||
* <li>APPROXIMATELY_OR_SINGLE_VALUE: "~5 miles" or "5 miles", depending on whether the number was the same before
|
||||
* rounding was applied</li>
|
||||
* <li>APPROXIMATELY: "~5 miles"</li>
|
||||
* <li>RANGE: "5-5 miles" (with collapse=UNIT)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The default value is APPROXIMATELY.
|
||||
*
|
||||
* @param identityFallback
|
||||
* The strategy to use when formatting two numbers that end up being the same.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) const &;
|
||||
|
||||
/**
|
||||
* Overload of identityFallback() for use on an rvalue reference.
|
||||
*
|
||||
* @param identityFallback
|
||||
* The strategy to use when formatting two numbers that end up being the same.
|
||||
* @return The fluent chain.
|
||||
* @see #identityFallback
|
||||
* @draft ICU 63
|
||||
*/
|
||||
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) &&;
|
||||
|
||||
/**
|
||||
* Sets the UErrorCode if an error occurred in the fluent chain.
|
||||
* Preserves older error codes in the outErrorCode.
|
||||
* @return TRUE if U_FAILURE(outErrorCode)
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UBool copyErrorTo(UErrorCode &outErrorCode) const {
|
||||
if (U_FAILURE(outErrorCode)) {
|
||||
// Do not overwrite the older error code
|
||||
return TRUE;
|
||||
}
|
||||
fMacros.copyErrorTo(outErrorCode);
|
||||
return U_FAILURE(outErrorCode);
|
||||
};
|
||||
|
||||
// NOTE: Uses default copy and move constructors.
|
||||
|
||||
private:
|
||||
impl::RangeMacroProps fMacros;
|
||||
|
||||
// Don't construct me directly! Use (Un)LocalizedNumberFormatter.
|
||||
NumberRangeFormatterSettings() = default;
|
||||
|
||||
friend class LocalizedNumberRangeFormatter;
|
||||
friend class UnlocalizedNumberRangeFormatter;
|
||||
};
|
||||
|
||||
/**
|
||||
* A NumberRangeFormatter that does not yet have a locale. In order to format, a locale must be specified.
|
||||
*
|
||||
* @see NumberRangeFormatter
|
||||
* @draft ICU 63
|
||||
*/
|
||||
class U_I18N_API UnlocalizedNumberRangeFormatter
|
||||
: public NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>, public UMemory {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Associate the given locale with the number range formatter. The locale is used for picking the
|
||||
* appropriate symbols, formats, and other data for number display.
|
||||
*
|
||||
* @param locale
|
||||
* The locale to use when loading data for number formatting.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter locale(const icu::Locale &locale) const &;
|
||||
|
||||
/**
|
||||
* Overload of locale() for use on an rvalue reference.
|
||||
*
|
||||
* @param locale
|
||||
* The locale to use when loading data for number formatting.
|
||||
* @return The fluent chain.
|
||||
* @see #locale
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter locale(const icu::Locale &locale) &&;
|
||||
|
||||
/**
|
||||
* Default constructor: puts the formatter into a valid but undefined state.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnlocalizedNumberRangeFormatter() = default;
|
||||
|
||||
/**
|
||||
* Returns a copy of this UnlocalizedNumberRangeFormatter.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnlocalizedNumberRangeFormatter(const UnlocalizedNumberRangeFormatter &other);
|
||||
|
||||
/**
|
||||
* Move constructor:
|
||||
* The source UnlocalizedNumberRangeFormatter will be left in a valid but undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnlocalizedNumberRangeFormatter(UnlocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Copy assignment operator.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnlocalizedNumberRangeFormatter& operator=(const UnlocalizedNumberRangeFormatter& other);
|
||||
|
||||
/**
|
||||
* Move assignment operator:
|
||||
* The source UnlocalizedNumberRangeFormatter will be left in a valid but undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnlocalizedNumberRangeFormatter& operator=(UnlocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
|
||||
|
||||
private:
|
||||
explicit UnlocalizedNumberRangeFormatter(
|
||||
const NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>& other);
|
||||
|
||||
explicit UnlocalizedNumberRangeFormatter(
|
||||
NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>&& src) U_NOEXCEPT;
|
||||
|
||||
// To give the fluent setters access to this class's constructor:
|
||||
friend class NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>;
|
||||
|
||||
// To give NumberRangeFormatter::with() access to this class's constructor:
|
||||
friend class NumberRangeFormatter;
|
||||
};
|
||||
|
||||
/**
|
||||
* A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
|
||||
*
|
||||
* @see NumberFormatter
|
||||
* @draft ICU 63
|
||||
*/
|
||||
class U_I18N_API LocalizedNumberRangeFormatter
|
||||
: public NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>, public UMemory {
|
||||
public:
|
||||
/**
|
||||
* Format the given Formattables to a string using the settings specified in the NumberRangeFormatter fluent setting
|
||||
* chain.
|
||||
*
|
||||
* @param first
|
||||
* The first number in the range, usually to the left in LTR locales.
|
||||
* @param second
|
||||
* The second number in the range, usually to the right in LTR locales.
|
||||
* @param status
|
||||
* Set if an error occurs while formatting.
|
||||
* @return A FormattedNumberRange object; call .toString() to get the string.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
FormattedNumberRange formatFormattableRange(
|
||||
const Formattable& first, const Formattable& second, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Default constructor: puts the formatter into a valid but undefined state.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter() = default;
|
||||
|
||||
/**
|
||||
* Returns a copy of this LocalizedNumberRangeFormatter.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter(const LocalizedNumberRangeFormatter &other);
|
||||
|
||||
/**
|
||||
* Move constructor:
|
||||
* The source LocalizedNumberRangeFormatter will be left in a valid but undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Copy assignment operator.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter& operator=(const LocalizedNumberRangeFormatter& other);
|
||||
|
||||
/**
|
||||
* Move assignment operator:
|
||||
* The source LocalizedNumberRangeFormatter will be left in a valid but undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
LocalizedNumberRangeFormatter& operator=(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
|
||||
|
||||
#ifndef U_HIDE_INTERNAL_API
|
||||
|
||||
/**
|
||||
* @param results
|
||||
* The results object. This method will mutate it to save the results.
|
||||
* @param status
|
||||
* Set if an error occurs while formatting.
|
||||
* @internal
|
||||
*/
|
||||
void formatImpl(impl::UFormattedNumberRangeData& results, bool equalBeforeRounding,
|
||||
UErrorCode& status) const;
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Destruct this LocalizedNumberRangeFormatter, cleaning up any memory it might own.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
~LocalizedNumberRangeFormatter();
|
||||
|
||||
private:
|
||||
// TODO: This is not thread-safe! Do NOT check this in without an atomic here.
|
||||
impl::NumberRangeFormatterImpl* fImpl = nullptr;
|
||||
|
||||
explicit LocalizedNumberRangeFormatter(
|
||||
const NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>& other);
|
||||
|
||||
explicit LocalizedNumberRangeFormatter(
|
||||
NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>&& src) U_NOEXCEPT;
|
||||
|
||||
LocalizedNumberRangeFormatter(const impl::RangeMacroProps ¯os, const Locale &locale);
|
||||
|
||||
LocalizedNumberRangeFormatter(impl::RangeMacroProps &¯os, const Locale &locale);
|
||||
|
||||
void clear();
|
||||
|
||||
// To give the fluent setters access to this class's constructor:
|
||||
friend class NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>;
|
||||
friend class NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>;
|
||||
|
||||
// To give UnlocalizedNumberRangeFormatter::locale() access to this class's constructor:
|
||||
friend class UnlocalizedNumberRangeFormatter;
|
||||
};
|
||||
|
||||
/**
|
||||
* The result of a number range formatting operation. This class allows the result to be exported in several data types,
|
||||
* including a UnicodeString and a FieldPositionIterator.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
class U_I18N_API FormattedNumberRange : public UMemory {
|
||||
public:
|
||||
/**
|
||||
* Returns a UnicodeString representation of the formatted number range.
|
||||
*
|
||||
* @param status
|
||||
* Set if an error occurs while formatting the number to the UnicodeString.
|
||||
* @return a UnicodeString containing the localized number range.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
UnicodeString toString(UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Appends the formatted number range to an Appendable.
|
||||
*
|
||||
* @param appendable
|
||||
* The Appendable to which to append the formatted number range string.
|
||||
* @param status
|
||||
* Set if an error occurs while formatting the number range to the Appendable.
|
||||
* @return The same Appendable, for chaining.
|
||||
* @draft ICU 63
|
||||
* @see Appendable
|
||||
*/
|
||||
Appendable &appendTo(Appendable &appendable, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
|
||||
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
|
||||
* the integer part, fraction part, or symbols.
|
||||
*
|
||||
* If both sides of the range have the same field, the field will occur twice, once before the
|
||||
* range separator and once after the range separator, if applicable.
|
||||
*
|
||||
* If a field occurs just once, calling this method will find that occurrence and return it. If a
|
||||
* field occurs multiple times, this method may be called repeatedly with the following pattern:
|
||||
*
|
||||
* <pre>
|
||||
* FieldPosition fpos(UNUM_INTEGER_FIELD);
|
||||
* while (formattedNumberRange.nextFieldPosition(fpos, status)) {
|
||||
* // do something with fpos.
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* This method is useful if you know which field to query. If you want all available field position
|
||||
* information, use #getAllFieldPositions().
|
||||
*
|
||||
* @param fieldPosition
|
||||
* Input+output variable. See {@link FormattedNumber#nextFieldPosition}.
|
||||
* @param status
|
||||
* Set if an error occurs while populating the FieldPosition.
|
||||
* @return TRUE if a new occurrence of the field was found; FALSE otherwise.
|
||||
* @draft ICU 63
|
||||
* @see UNumberFormatFields
|
||||
*/
|
||||
UBool nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Export the formatted number range to a FieldPositionIterator. This allows you to determine which characters in
|
||||
* the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and sign.
|
||||
*
|
||||
* If information on only one field is needed, use #nextFieldPosition() instead.
|
||||
*
|
||||
* @param iterator
|
||||
* The FieldPositionIterator to populate with all of the fields present in the formatted number.
|
||||
* @param status
|
||||
* Set if an error occurs while populating the FieldPositionIterator.
|
||||
* @draft ICU 63
|
||||
* @see UNumberFormatFields
|
||||
*/
|
||||
void getAllFieldPositions(FieldPositionIterator &iterator, UErrorCode &status) const;
|
||||
|
||||
/**
|
||||
* Export the first formatted number as a decimal number. This endpoint
|
||||
* is useful for obtaining the exact number being printed after scaling
|
||||
* and rounding have been applied by the number range formatting pipeline.
|
||||
*
|
||||
* The syntax of the unformatted number is a "numeric string"
|
||||
* as defined in the Decimal Arithmetic Specification, available at
|
||||
* http://speleotrove.com/decimal
|
||||
*
|
||||
* @return A decimal representation of the first formatted number.
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
* @see #getSecondDecimal
|
||||
*/
|
||||
UnicodeString getFirstDecimal(UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Export the second formatted number as a decimal number. This endpoint
|
||||
* is useful for obtaining the exact number being printed after scaling
|
||||
* and rounding have been applied by the number range formatting pipeline.
|
||||
*
|
||||
* The syntax of the unformatted number is a "numeric string"
|
||||
* as defined in the Decimal Arithmetic Specification, available at
|
||||
* http://speleotrove.com/decimal
|
||||
*
|
||||
* @return A decimal representation of the second formatted number.
|
||||
* @draft ICU 63
|
||||
* @see NumberRangeFormatter
|
||||
* @see #getFirstDecimal
|
||||
*/
|
||||
UnicodeString getSecondDecimal(UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Returns whether the pair of numbers was successfully formatted as a range or whether an identity fallback was
|
||||
* used. For example, if the first and second number were the same either before or after rounding occurred, an
|
||||
* identity fallback was used.
|
||||
*
|
||||
* @return An indication the resulting identity situation in the formatted number range.
|
||||
* @draft ICU 63
|
||||
* @see UNumberRangeIdentityFallback
|
||||
*/
|
||||
UNumberRangeIdentityResult getIdentityResult(UErrorCode& status) const;
|
||||
|
||||
/**
|
||||
* Copying not supported; use move constructor instead.
|
||||
*/
|
||||
FormattedNumberRange(const FormattedNumberRange&) = delete;
|
||||
|
||||
/**
|
||||
* Copying not supported; use move assignment instead.
|
||||
*/
|
||||
FormattedNumberRange& operator=(const FormattedNumberRange&) = delete;
|
||||
|
||||
/**
|
||||
* Move constructor:
|
||||
* Leaves the source FormattedNumberRange in an undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
FormattedNumberRange(FormattedNumberRange&& src) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Move assignment:
|
||||
* Leaves the source FormattedNumberRange in an undefined state.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
FormattedNumberRange& operator=(FormattedNumberRange&& src) U_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* Destruct an instance of FormattedNumberRange, cleaning up any memory it might own.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
~FormattedNumberRange();
|
||||
|
||||
private:
|
||||
// Can't use LocalPointer because UFormattedNumberRangeData is forward-declared
|
||||
const impl::UFormattedNumberRangeData *fResults;
|
||||
|
||||
// Error code for the terminal methods
|
||||
UErrorCode fErrorCode;
|
||||
|
||||
/**
|
||||
* Internal constructor from data type. Adopts the data pointer.
|
||||
* @internal
|
||||
*/
|
||||
explicit FormattedNumberRange(impl::UFormattedNumberRangeData *results)
|
||||
: fResults(results), fErrorCode(U_ZERO_ERROR) {};
|
||||
|
||||
explicit FormattedNumberRange(UErrorCode errorCode)
|
||||
: fResults(nullptr), fErrorCode(errorCode) {};
|
||||
|
||||
void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
|
||||
|
||||
// To give LocalizedNumberRangeFormatter format methods access to this class's constructor:
|
||||
friend class LocalizedNumberRangeFormatter;
|
||||
};
|
||||
|
||||
/**
|
||||
* See the main description in numberrangeformatter.h for documentation and examples.
|
||||
*
|
||||
* @draft ICU 63
|
||||
*/
|
||||
class U_I18N_API NumberRangeFormatter final {
|
||||
public:
|
||||
/**
|
||||
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is not currently
|
||||
* known at the call site.
|
||||
*
|
||||
* @return An {@link UnlocalizedNumberRangeFormatter}, to be used for chaining.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
static UnlocalizedNumberRangeFormatter with();
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
|
||||
* site.
|
||||
*
|
||||
* @param locale
|
||||
* The locale from which to load formats and symbols for number range formatting.
|
||||
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
static LocalizedNumberRangeFormatter withLocale(const Locale &locale);
|
||||
|
||||
/**
|
||||
* Use factory methods instead of the constructor to create a NumberFormatter.
|
||||
*/
|
||||
NumberRangeFormatter() = delete;
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
U_NAMESPACE_END
|
||||
|
||||
#endif // U_HIDE_DRAFT_API
|
||||
|
||||
#endif // __NUMBERRANGEFORMATTER_H__
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
@ -48,8 +48,8 @@ int main(int argc, char **argv)
|
||||
/* Protos */
|
||||
static void usage(void);
|
||||
static void version(void);
|
||||
static void date(UDate when, const UChar *tz, UDateFormatStyle style, const char *format, UErrorCode *status);
|
||||
static UDate getWhen(const char *millis, const char *seconds, const char *format, UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status);
|
||||
static void date(UDate when, const UChar *tz, UDateFormatStyle style, const char *format, const char *locale, UErrorCode *status);
|
||||
static UDate getWhen(const char *millis, const char *seconds, const char *format, const char *locale, UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status);
|
||||
|
||||
UConverter *cnv = NULL;
|
||||
|
||||
@ -74,6 +74,7 @@ main(int argc,
|
||||
UDateFormatStyle style = UDAT_DEFAULT;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const char *format = NULL;
|
||||
const char *locale = NULL;
|
||||
char *parse = NULL;
|
||||
char *seconds = NULL;
|
||||
char *millis = NULL;
|
||||
@ -132,6 +133,12 @@ main(int argc,
|
||||
parse = argv[optInd];
|
||||
}
|
||||
}
|
||||
else if (strcmp(arg, "-L") == 0) {
|
||||
if (optInd + 1 < argc) {
|
||||
optInd++;
|
||||
locale = argv[optInd];
|
||||
}
|
||||
}
|
||||
/* POSIX.1 says all arguments after -- are not options */
|
||||
else if(strcmp(arg, "--") == 0) {
|
||||
/* skip the -- */
|
||||
@ -162,13 +169,13 @@ main(int argc,
|
||||
}
|
||||
|
||||
/* get the 'when' (or now) */
|
||||
when = getWhen(millis, seconds, format, style, parse, tz, &status);
|
||||
when = getWhen(millis, seconds, format, locale, style, parse, tz, &status);
|
||||
if(parse != NULL) {
|
||||
format = FORMAT_MILLIS; /* output in millis */
|
||||
}
|
||||
|
||||
/* print the date */
|
||||
date(when, tz, style, format, &status);
|
||||
date(when, tz, style, format, locale, &status);
|
||||
|
||||
ucnv_close(cnv);
|
||||
|
||||
@ -194,6 +201,7 @@ usage()
|
||||
puts(" -r <seconds> Use <seconds> as the time (Epoch 1970) rather than now.");
|
||||
puts(" -R <millis> Use <millis> as the time (Epoch 1970) rather than now.");
|
||||
puts(" -P <string> Parse <string> as the time, output in millis format.");
|
||||
puts(" -L <string> Use the locale <string> instead of the default ICU locale.");
|
||||
}
|
||||
|
||||
/* Version information */
|
||||
@ -245,6 +253,7 @@ date(UDate when,
|
||||
const UChar *tz,
|
||||
UDateFormatStyle style,
|
||||
const char *format,
|
||||
const char *locale,
|
||||
UErrorCode *status )
|
||||
{
|
||||
UChar *s = 0;
|
||||
@ -264,7 +273,7 @@ date(UDate when,
|
||||
}
|
||||
}
|
||||
|
||||
fmt = udat_open(style, style, 0, tz, -1,NULL,0, status);
|
||||
fmt = udat_open(style, style, locale, tz, -1,NULL,0, status);
|
||||
if ( format != NULL ) {
|
||||
charsToUCharsDefault(uFormat,sizeof(uFormat)/sizeof(uFormat[0]),format,-1,status);
|
||||
udat_applyPattern(fmt,FALSE,uFormat,-1);
|
||||
@ -292,7 +301,7 @@ date(UDate when,
|
||||
free(s);
|
||||
}
|
||||
|
||||
static UDate getWhen(const char *millis, const char *seconds, const char *format,
|
||||
static UDate getWhen(const char *millis, const char *seconds, const char *format, const char *locale,
|
||||
UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status) {
|
||||
UDateFormat *fmt = NULL;
|
||||
UChar uFormat[100];
|
||||
@ -319,7 +328,7 @@ static UDate getWhen(const char *millis, const char *seconds, const char *format
|
||||
}
|
||||
}
|
||||
|
||||
fmt = udat_open(style, style, 0, tz, -1,NULL,0, status);
|
||||
fmt = udat_open(style, style, locale, tz, -1,NULL,0, status);
|
||||
if ( format != NULL ) {
|
||||
charsToUCharsDefault(uFormat,sizeof(uFormat)/sizeof(uFormat[0]), format,-1,status);
|
||||
udat_applyPattern(fmt,FALSE,uFormat,-1);
|
||||
|
@ -251,6 +251,7 @@ void addLocaleTest(TestNode** root)
|
||||
TESTCASE(TestLikelySubtags);
|
||||
TESTCASE(TestToLanguageTag);
|
||||
TESTCASE(TestForLanguageTag);
|
||||
TESTCASE(TestInvalidLanguageTag);
|
||||
TESTCASE(TestTrailingNull);
|
||||
TESTCASE(TestUnicodeDefines);
|
||||
TESTCASE(TestEnglishExemplarCharacters);
|
||||
@ -6030,6 +6031,9 @@ static const struct {
|
||||
{"ja-u-ijkl-efgh-abcd-ca-japanese-xx-yyy-zzz-kn", "ja@attribute=abcd-efgh-ijkl;calendar=japanese;colnumeric=yes;xx=yyy-zzz", FULL_LENGTH},
|
||||
{"de-u-xc-xphonebk-co-phonebk-ca-buddhist-mo-very-lo-extensi-xd-that-de-should-vc-probably-xz-killthebuffer",
|
||||
"de@calendar=buddhist;collation=phonebook;de=should;lo=extensi;mo=very;vc=probably;xc=xphonebk;xd=that;xz=yes", 91},
|
||||
{"de-1901-1901", "de__1901", 7},
|
||||
{"de-DE-1901-1901", "de_DE_1901", 10},
|
||||
{"en-a-bbb-a-ccc", "en@a=bbb", 8},
|
||||
/* #12761 */
|
||||
{"en-a-bar-u-baz", "en@a=bar;attribute=baz", FULL_LENGTH},
|
||||
{"en-a-bar-u-baz-x-u-foo", "en@a=bar;attribute=baz;x=u-foo", FULL_LENGTH},
|
||||
@ -6047,6 +6051,11 @@ static const struct {
|
||||
{"zh-cmn-TW", "cmn_TW", FULL_LENGTH},
|
||||
{"zh-x_t-ab", "zh", 2},
|
||||
{"zh-hans-cn-u-ca-x_t-u", "zh_Hans_CN@calendar=yes", 15},
|
||||
/* #20140 dupe keys in U-extension */
|
||||
{"zh-u-ca-chinese-ca-gregory", "zh@calendar=chinese", FULL_LENGTH},
|
||||
{"zh-u-ca-gregory-co-pinyin-ca-chinese", "zh@calendar=gregorian;collation=pinyin", FULL_LENGTH},
|
||||
{"de-latn-DE-1901-u-co-phonebk-co-pinyin-ca-gregory", "de_Latn_DE_1901@calendar=gregorian;collation=phonebook", FULL_LENGTH},
|
||||
{"th-u-kf-nu-thai-kf-false", "th@colcasefirst=yes;numbers=thai", FULL_LENGTH},
|
||||
{NULL, NULL, 0}
|
||||
};
|
||||
|
||||
@ -6081,6 +6090,35 @@ static void TestForLanguageTag(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* See https://unicode-org.atlassian.net/browse/ICU-20149 .
|
||||
* Depending on the resolution of that bug, this test may have
|
||||
* to be revised.
|
||||
*/
|
||||
static void TestInvalidLanguageTag(void) {
|
||||
static const char* invalid_lang_tags[] = {
|
||||
"zh-u-foo-foo-co-pinyin", /* duplicate attribute in U extension */
|
||||
"zh-cmn-hans-u-foo-foo-co-pinyin", /* duplicate attribute in U extension */
|
||||
#if 0
|
||||
/*
|
||||
* These do not lead to an error. Instead, parsing stops at the 1st
|
||||
* invalid subtag.
|
||||
*/
|
||||
"de-DE-1901-1901", /* duplicate variant */
|
||||
"en-a-bbb-a-ccc", /* duplicate extension */
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
char locale[256];
|
||||
for (const char** tag = invalid_lang_tags; *tag != NULL; tag++) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
uloc_forLanguageTag(*tag, locale, sizeof(locale), NULL, &status);
|
||||
if (status != U_ILLEGAL_ARGUMENT_ERROR) {
|
||||
log_err("Error returned by uloc_forLanguageTag for input language tag [%s] : %s - expected error: %s\n",
|
||||
*tag, u_errorName(status), u_errorName(U_ILLEGAL_ARGUMENT_ERROR));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void TestToUnicodeLocaleKey(void)
|
||||
{
|
||||
/* $IN specifies the result should be the input pointer itself */
|
||||
|
@ -123,6 +123,7 @@ static void TestLikelySubtags(void);
|
||||
* lanuage tag
|
||||
*/
|
||||
static void TestForLanguageTag(void);
|
||||
static void TestInvalidLanguageTag(void);
|
||||
static void TestToLanguageTag(void);
|
||||
|
||||
static void TestToUnicodeLocaleKey(void);
|
||||
|
@ -164,7 +164,6 @@ library: common
|
||||
ubidi ushape ubiditransform
|
||||
listformatter
|
||||
resourcebundle service_registration resbund_cnv ures_cnv icudataver ucat
|
||||
loclikely
|
||||
currency
|
||||
locale_display_names2
|
||||
conversion converter_selector ucnv_set ucnvdisp
|
||||
@ -383,7 +382,7 @@ group: cstr
|
||||
group: uscript
|
||||
uscript.o # uscript_getCode() accepts a locale ID and loads its script code data
|
||||
deps
|
||||
propname loclikely
|
||||
propname resourcebundle
|
||||
|
||||
group: uscript_props # script metadata properties
|
||||
uscript_props.o
|
||||
@ -583,7 +582,7 @@ group: locale_display_names2
|
||||
group: currency
|
||||
ucurr.o
|
||||
deps
|
||||
loclikely resourcebundle ulist ustring_case_locale
|
||||
resourcebundle ulist ustring_case_locale
|
||||
stdlib_qsort # for ucurr.o (which does not use ICU's uarrsort.o)
|
||||
static_unicode_sets usetiter
|
||||
|
||||
@ -592,11 +591,6 @@ group: icudataver # u_getDataVersion()
|
||||
deps
|
||||
resourcebundle
|
||||
|
||||
group: loclikely
|
||||
loclikely.o
|
||||
deps
|
||||
resourcebundle uscript_props propname
|
||||
|
||||
group: locresdata
|
||||
# This was intended to collect locale functions that load resource bundle data.
|
||||
# See the resourcebundle group about what else loads data.
|
||||
@ -631,9 +625,12 @@ group: resourcebundle
|
||||
locid.o locmap.o wintz.o
|
||||
# Do we need class LocaleBased? http://bugs.icu-project.org/trac/ticket/8608
|
||||
locbased.o
|
||||
loclikely.o
|
||||
deps
|
||||
udata ucol_swp
|
||||
sort stringenumeration uhash uvector
|
||||
uscript_props propname
|
||||
bytesinkutil
|
||||
|
||||
group: udata
|
||||
udata.o ucmndata.o udatamem.o
|
||||
@ -832,7 +829,6 @@ group: localedata
|
||||
deps
|
||||
uniset_props resourcebundle
|
||||
uset_props # TODO: change to using C++ UnicodeSet, remove this dependency
|
||||
loclikely
|
||||
|
||||
group: genderinfo
|
||||
gender.o
|
||||
|
@ -66,7 +66,7 @@ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \
|
||||
numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \
|
||||
numbertest_stringbuilder.o numbertest_stringsegment.o \
|
||||
numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
|
||||
static_unisets_test.o numfmtdatadriventest.o erarulestest.o
|
||||
static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o
|
||||
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
|
||||
|
@ -256,6 +256,7 @@
|
||||
<ClCompile Include="numbertest_parse.cpp" />
|
||||
<ClCompile Include="numbertest_doubleconversion.cpp" />
|
||||
<ClCompile Include="numbertest_skeletons.cpp" />
|
||||
<ClCompile Include="numbertest_range.cpp" />
|
||||
<ClCompile Include="numfmtst.cpp" />
|
||||
<ClCompile Include="numfmtdatadriventest.cpp" />
|
||||
<ClCompile Include="numrgts.cpp" />
|
||||
|
@ -289,6 +289,9 @@
|
||||
<ClCompile Include="numbertest_skeletons.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="numbertest_range.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="numfmtst.cpp">
|
||||
<Filter>formatting</Filter>
|
||||
</ClCompile>
|
||||
|
@ -6,7 +6,12 @@
|
||||
* others. All Rights Reserved.
|
||||
********************************************************************/
|
||||
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#include "loctest.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/decimfmt.h"
|
||||
#include "unicode/ucurr.h"
|
||||
#include "unicode/smpdtfmt.h"
|
||||
@ -220,9 +225,18 @@ void LocaleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, c
|
||||
#endif
|
||||
TESTCASE_AUTO(TestSetIsBogus);
|
||||
TESTCASE_AUTO(TestParallelAPIValues);
|
||||
TESTCASE_AUTO(TestAddLikelySubtags);
|
||||
TESTCASE_AUTO(TestMinimizeSubtags);
|
||||
TESTCASE_AUTO(TestKeywordVariants);
|
||||
TESTCASE_AUTO(TestCreateUnicodeKeywords);
|
||||
TESTCASE_AUTO(TestKeywordVariantParsing);
|
||||
TESTCASE_AUTO(TestCreateKeywordSet);
|
||||
TESTCASE_AUTO(TestCreateUnicodeKeywordSet);
|
||||
TESTCASE_AUTO(TestGetKeywordValueStdString);
|
||||
TESTCASE_AUTO(TestGetUnicodeKeywordValueStdString);
|
||||
TESTCASE_AUTO(TestSetKeywordValue);
|
||||
TESTCASE_AUTO(TestSetKeywordValueStringPiece);
|
||||
TESTCASE_AUTO(TestSetUnicodeKeywordValueStringPiece);
|
||||
TESTCASE_AUTO(TestGetBaseName);
|
||||
#if !UCONFIG_NO_FILE_IO
|
||||
TESTCASE_AUTO(TestGetLocale);
|
||||
@ -236,6 +250,8 @@ void LocaleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, c
|
||||
TESTCASE_AUTO(TestBug13554);
|
||||
TESTCASE_AUTO(TestForLanguageTag);
|
||||
TESTCASE_AUTO(TestToLanguageTag);
|
||||
TESTCASE_AUTO(TestMoveAssign);
|
||||
TESTCASE_AUTO(TestMoveCtor);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
@ -1601,6 +1617,34 @@ LocaleTest::TestSetIsBogus() {
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTest::TestAddLikelySubtags() {
|
||||
IcuTestErrorCode status(*this, "TestAddLikelySubtags()");
|
||||
|
||||
static const Locale min("sv");
|
||||
static const Locale max("sv_Latn_SE");
|
||||
|
||||
Locale result(min);
|
||||
result.addLikelySubtags(status);
|
||||
status.errIfFailureAndReset("\"%s\"", min.getName());
|
||||
assertEquals("addLikelySubtags", max.getName(), result.getName());
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTest::TestMinimizeSubtags() {
|
||||
IcuTestErrorCode status(*this, "TestMinimizeSubtags()");
|
||||
|
||||
static const Locale max("zh_Hant_TW");
|
||||
static const Locale min("zh_TW");
|
||||
|
||||
Locale result(max);
|
||||
result.minimizeSubtags(status);
|
||||
status.errIfFailureAndReset("\"%s\"", max.getName());
|
||||
assertEquals("minimizeSubtags", min.getName(), result.getName());
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTest::TestKeywordVariants(void) {
|
||||
static const struct {
|
||||
@ -1708,6 +1752,55 @@ LocaleTest::TestKeywordVariants(void) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTest::TestCreateUnicodeKeywords(void) {
|
||||
IcuTestErrorCode status(*this, "TestCreateUnicodeKeywords()");
|
||||
|
||||
static const Locale l("de@calendar=buddhist;collation=phonebook");
|
||||
|
||||
LocalPointer<StringEnumeration> keys(l.createUnicodeKeywords(status));
|
||||
status.errIfFailureAndReset("\"%s\"", l.getName());
|
||||
|
||||
const char* key;
|
||||
int32_t resultLength;
|
||||
|
||||
key = keys->next(&resultLength, status);
|
||||
status.errIfFailureAndReset("key #1");
|
||||
assertEquals("resultLength", 2, resultLength);
|
||||
assertTrue("key != nullptr", key != nullptr);
|
||||
assertEquals("calendar", "ca", key);
|
||||
|
||||
key = keys->next(&resultLength, status);
|
||||
status.errIfFailureAndReset("key #2");
|
||||
assertEquals("resultLength", 2, resultLength);
|
||||
assertTrue("key != nullptr", key != nullptr);
|
||||
assertEquals("collation", "co", key);
|
||||
|
||||
key = keys->next(&resultLength, status);
|
||||
status.errIfFailureAndReset("end of keys");
|
||||
assertEquals("resultLength", 0, resultLength);
|
||||
assertTrue("key == nullptr", key == nullptr);
|
||||
|
||||
const UnicodeString* skey;
|
||||
keys->reset(status); // KeywordEnumeration::reset() never touches status.
|
||||
|
||||
skey = keys->snext(status);
|
||||
status.errIfFailureAndReset("skey #1");
|
||||
assertTrue("skey != nullptr", skey != nullptr);
|
||||
assertEquals("calendar", "ca", *skey);
|
||||
|
||||
skey = keys->snext(status);
|
||||
status.errIfFailureAndReset("skey #2");
|
||||
assertTrue("skey != nullptr", skey != nullptr);
|
||||
assertEquals("collation", "co", *skey);
|
||||
|
||||
skey = keys->snext(status);
|
||||
status.errIfFailureAndReset("end of keys");
|
||||
assertTrue("skey == nullptr", skey == nullptr);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LocaleTest::TestKeywordVariantParsing(void) {
|
||||
static const struct {
|
||||
@ -1739,6 +1832,74 @@ LocaleTest::TestKeywordVariantParsing(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestCreateKeywordSet(void) {
|
||||
IcuTestErrorCode status(*this, "TestCreateKeywordSet()");
|
||||
|
||||
static const Locale l("de@calendar=buddhist;collation=phonebook");
|
||||
|
||||
std::set<std::string> result;
|
||||
l.getKeywords<std::string>(
|
||||
std::insert_iterator<decltype(result)>(result, result.begin()),
|
||||
status);
|
||||
status.errIfFailureAndReset("\"%s\"", l.getName());
|
||||
|
||||
assertEquals("set::size()", 2, result.size());
|
||||
assertTrue("set::find(\"calendar\")",
|
||||
result.find("calendar") != result.end());
|
||||
assertTrue("set::find(\"collation\")",
|
||||
result.find("collation") != result.end());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestCreateUnicodeKeywordSet(void) {
|
||||
IcuTestErrorCode status(*this, "TestCreateUnicodeKeywordSet()");
|
||||
|
||||
static const Locale l("de@calendar=buddhist;collation=phonebook");
|
||||
|
||||
std::set<std::string> result;
|
||||
l.getUnicodeKeywords<std::string>(
|
||||
std::insert_iterator<decltype(result)>(result, result.begin()),
|
||||
status);
|
||||
status.errIfFailureAndReset("\"%s\"", l.getName());
|
||||
|
||||
assertEquals("set::size()", 2, result.size());
|
||||
assertTrue("set::find(\"ca\")",
|
||||
result.find("ca") != result.end());
|
||||
assertTrue("set::find(\"co\")",
|
||||
result.find("co") != result.end());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestGetKeywordValueStdString(void) {
|
||||
IcuTestErrorCode status(*this, "TestGetKeywordValueStdString()");
|
||||
|
||||
static const char tag[] = "fa-u-nu-latn";
|
||||
static const char keyword[] = "numbers";
|
||||
static const char expected[] = "latn";
|
||||
|
||||
Locale l = Locale::forLanguageTag(tag, status);
|
||||
status.errIfFailureAndReset("\"%s\"", tag);
|
||||
|
||||
std::string result = l.getKeywordValue<std::string>(keyword, status);
|
||||
status.errIfFailureAndReset("\"%s\"", keyword);
|
||||
assertEquals(keyword, expected, result.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestGetUnicodeKeywordValueStdString(void) {
|
||||
IcuTestErrorCode status(*this, "TestGetUnicodeKeywordValueStdString()");
|
||||
|
||||
static const char keyword[] = "co";
|
||||
static const char expected[] = "phonebk";
|
||||
|
||||
static const Locale l("de@calendar=buddhist;collation=phonebook");
|
||||
|
||||
std::string result = l.getUnicodeKeywordValue<std::string>(keyword, status);
|
||||
status.errIfFailureAndReset("\"%s\"", keyword);
|
||||
assertEquals(keyword, expected, result.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestSetKeywordValue(void) {
|
||||
static const struct {
|
||||
@ -1774,6 +1935,33 @@ LocaleTest::TestSetKeywordValue(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestSetKeywordValueStringPiece(void) {
|
||||
IcuTestErrorCode status(*this, "TestSetKeywordValueStringPiece()");
|
||||
Locale l(Locale::getGerman());
|
||||
|
||||
l.setKeywordValue(StringPiece("collation"), StringPiece("phonebook"), status);
|
||||
l.setKeywordValue(StringPiece("calendarxxx", 8), StringPiece("buddhistxxx", 8), status);
|
||||
|
||||
static const char expected[] = "de@calendar=buddhist;collation=phonebook";
|
||||
assertEquals("", expected, l.getName());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestSetUnicodeKeywordValueStringPiece(void) {
|
||||
IcuTestErrorCode status(*this, "TestSetUnicodeKeywordValueStringPiece()");
|
||||
Locale l(Locale::getGerman());
|
||||
|
||||
l.setUnicodeKeywordValue(StringPiece("co"), StringPiece("phonebk"), status);
|
||||
status.errIfFailureAndReset();
|
||||
|
||||
l.setUnicodeKeywordValue(StringPiece("caxxx", 2), StringPiece("buddhistxxx", 8), status);
|
||||
status.errIfFailureAndReset();
|
||||
|
||||
static const char expected[] = "de@calendar=buddhist;collation=phonebook";
|
||||
assertEquals("", expected, l.getName());
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTest::TestGetBaseName(void) {
|
||||
static const struct {
|
||||
@ -2845,3 +3033,95 @@ void LocaleTest::TestToLanguageTag() {
|
||||
assertEquals("bogus", U_ILLEGAL_ARGUMENT_ERROR, status.reset());
|
||||
assertTrue(result_bogus.c_str(), result_bogus.empty());
|
||||
}
|
||||
|
||||
void LocaleTest::TestMoveAssign() {
|
||||
// ULOC_FULLNAME_CAPACITY == 157 (uloc.h)
|
||||
Locale l1("de@collation=phonebook;x="
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbzz");
|
||||
|
||||
Locale l2;
|
||||
{
|
||||
Locale l3(l1);
|
||||
assertTrue("l1 == l3", l1 == l3);
|
||||
l2 = std::move(l3);
|
||||
assertTrue("l1 == l2", l1 == l2);
|
||||
assertTrue("l2 != l3", l2.getName() != l3.getName());
|
||||
}
|
||||
|
||||
// This should remain true also after l3 has been destructed.
|
||||
assertTrue("l1 == l2, again", l1 == l2);
|
||||
|
||||
Locale l4("de@collation=phonebook");
|
||||
|
||||
Locale l5;
|
||||
{
|
||||
Locale l6(l4);
|
||||
assertTrue("l4 == l6", l4 == l6);
|
||||
l5 = std::move(l6);
|
||||
assertTrue("l4 == l5", l4 == l5);
|
||||
assertTrue("l5 != l6", l5.getName() != l6.getName());
|
||||
}
|
||||
|
||||
// This should remain true also after l6 has been destructed.
|
||||
assertTrue("l4 == l5, again", l4 == l5);
|
||||
|
||||
Locale l7("vo_Cyrl_AQ_EURO");
|
||||
|
||||
Locale l8;
|
||||
{
|
||||
Locale l9(l7);
|
||||
assertTrue("l7 == l9", l7 == l9);
|
||||
l8 = std::move(l9);
|
||||
assertTrue("l7 == l8", l7 == l8);
|
||||
assertTrue("l8 != l9", l8.getName() != l9.getName());
|
||||
}
|
||||
|
||||
// This should remain true also after l9 has been destructed.
|
||||
assertTrue("l7 == l8, again", l7 == l8);
|
||||
|
||||
assertEquals("language", l7.getLanguage(), l8.getLanguage());
|
||||
assertEquals("script", l7.getScript(), l8.getScript());
|
||||
assertEquals("country", l7.getCountry(), l8.getCountry());
|
||||
assertEquals("variant", l7.getVariant(), l8.getVariant());
|
||||
assertEquals("bogus", l7.isBogus(), l8.isBogus());
|
||||
}
|
||||
|
||||
void LocaleTest::TestMoveCtor() {
|
||||
// ULOC_FULLNAME_CAPACITY == 157 (uloc.h)
|
||||
Locale l1("de@collation=phonebook;x="
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
|
||||
"aaaaabbbbbzz");
|
||||
|
||||
Locale l3(l1);
|
||||
assertTrue("l1 == l3", l1 == l3);
|
||||
Locale l2(std::move(l3));
|
||||
assertTrue("l1 == l2", l1 == l2);
|
||||
assertTrue("l2 != l3", l2.getName() != l3.getName());
|
||||
|
||||
Locale l4("de@collation=phonebook");
|
||||
|
||||
Locale l6(l4);
|
||||
assertTrue("l4 == l6", l4 == l6);
|
||||
Locale l5(std::move(l6));
|
||||
assertTrue("l4 == l5", l4 == l5);
|
||||
assertTrue("l5 != l6", l5.getName() != l6.getName());
|
||||
|
||||
Locale l7("vo_Cyrl_AQ_EURO");
|
||||
|
||||
Locale l9(l7);
|
||||
assertTrue("l7 == l9", l7 == l9);
|
||||
Locale l8(std::move(l9));
|
||||
assertTrue("l7 == l8", l7 == l8);
|
||||
assertTrue("l8 != l9", l8.getName() != l9.getName());
|
||||
|
||||
assertEquals("language", l7.getLanguage(), l8.getLanguage());
|
||||
assertEquals("script", l7.getScript(), l8.getScript());
|
||||
assertEquals("country", l7.getCountry(), l8.getCountry());
|
||||
assertEquals("variant", l7.getVariant(), l8.getVariant());
|
||||
assertEquals("bogus", l7.isBogus(), l8.isBogus());
|
||||
}
|
||||
|
@ -72,14 +72,21 @@ public:
|
||||
|
||||
void TestVariantParsing(void);
|
||||
|
||||
/* Test getting keyword enumeratin */
|
||||
/* Test getting keyword enumeration */
|
||||
void TestKeywordVariants(void);
|
||||
void TestCreateUnicodeKeywords(void);
|
||||
|
||||
/* Test getting keyword values */
|
||||
void TestKeywordVariantParsing(void);
|
||||
void TestCreateKeywordSet(void);
|
||||
void TestCreateUnicodeKeywordSet(void);
|
||||
void TestGetKeywordValueStdString(void);
|
||||
void TestGetUnicodeKeywordValueStdString(void);
|
||||
|
||||
/* Test setting keyword values */
|
||||
void TestSetKeywordValue(void);
|
||||
void TestSetKeywordValueStringPiece(void);
|
||||
void TestSetUnicodeKeywordValueStringPiece(void);
|
||||
|
||||
/* Test getting the locale base name */
|
||||
void TestGetBaseName(void);
|
||||
@ -108,9 +115,15 @@ public:
|
||||
void TestBug13277();
|
||||
void TestBug13554();
|
||||
|
||||
void TestAddLikelySubtags();
|
||||
void TestMinimizeSubtags();
|
||||
|
||||
void TestForLanguageTag();
|
||||
void TestToLanguageTag();
|
||||
|
||||
void TestMoveAssign();
|
||||
void TestMoveCtor();
|
||||
|
||||
private:
|
||||
void _checklocs(const char* label,
|
||||
const char* req,
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "number_affixutils.h"
|
||||
#include "numparse_stringsegment.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/numberformatter.h"
|
||||
#include "unicode/numberrangeformatter.h"
|
||||
|
||||
using namespace icu::number;
|
||||
using namespace icu::number::impl;
|
||||
@ -244,6 +246,54 @@ class NumberSkeletonTest : public IntlTest {
|
||||
void expectedErrorSkeleton(const char16_t** cases, int32_t casesLen);
|
||||
};
|
||||
|
||||
class NumberRangeFormatterTest : public IntlTest {
|
||||
public:
|
||||
NumberRangeFormatterTest();
|
||||
NumberRangeFormatterTest(UErrorCode &status);
|
||||
|
||||
void testSanity();
|
||||
void testBasic();
|
||||
void testCollapse();
|
||||
void testIdentity();
|
||||
void testDifferentFormatters();
|
||||
void testPlurals();
|
||||
void testCopyMove();
|
||||
|
||||
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
|
||||
|
||||
private:
|
||||
CurrencyUnit USD;
|
||||
CurrencyUnit GBP;
|
||||
CurrencyUnit PTE;
|
||||
|
||||
MeasureUnit METER;
|
||||
MeasureUnit KILOMETER;
|
||||
MeasureUnit FAHRENHEIT;
|
||||
MeasureUnit KELVIN;
|
||||
|
||||
void assertFormatRange(
|
||||
const char16_t* message,
|
||||
const UnlocalizedNumberRangeFormatter& f,
|
||||
Locale locale,
|
||||
const char16_t* expected_10_50,
|
||||
const char16_t* expected_49_51,
|
||||
const char16_t* expected_50_50,
|
||||
const char16_t* expected_00_30,
|
||||
const char16_t* expected_00_00,
|
||||
const char16_t* expected_30_3K,
|
||||
const char16_t* expected_30K_50K,
|
||||
const char16_t* expected_49K_51K,
|
||||
const char16_t* expected_50K_50K,
|
||||
const char16_t* expected_50K_50M);
|
||||
|
||||
void assertFormattedRangeEquals(
|
||||
const char16_t* message,
|
||||
const LocalizedNumberRangeFormatter& l,
|
||||
double first,
|
||||
double second,
|
||||
const char16_t* expected);
|
||||
};
|
||||
|
||||
|
||||
// NOTE: This macro is identical to the one in itformat.cpp
|
||||
#define TESTCLASS(id, TestClass) \
|
||||
@ -276,6 +326,7 @@ class NumberTest : public IntlTest {
|
||||
TESTCLASS(8, StringSegmentTest);
|
||||
TESTCLASS(9, NumberParserTest);
|
||||
TESTCLASS(10, NumberSkeletonTest);
|
||||
TESTCLASS(11, NumberRangeFormatterTest);
|
||||
default: name = ""; break; // needed to end loop
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
// Horrible workaround for the lack of a status code in the constructor...
|
||||
// (Also affects numbertest_range.cpp)
|
||||
UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR;
|
||||
|
||||
NumberFormatterApiTest::NumberFormatterApiTest()
|
||||
@ -2371,8 +2372,15 @@ void NumberFormatterApiTest::copyMove() {
|
||||
assertTrue("[constructor] Source should be reset after move", l1.getCompiled() == nullptr);
|
||||
|
||||
// Reset l1 and l2 to check for macro-props copying for behavior testing
|
||||
// Make the test more interesting: also warm them up with a compiled formatter.
|
||||
l1 = NumberFormatter::withLocale("en");
|
||||
l1.formatInt(1, status);
|
||||
l1.formatInt(1, status);
|
||||
l1.formatInt(1, status);
|
||||
l2 = NumberFormatter::withLocale("en");
|
||||
l2.formatInt(1, status);
|
||||
l2.formatInt(1, status);
|
||||
l2.formatInt(1, status);
|
||||
|
||||
// Copy assignment
|
||||
l1 = l3;
|
||||
|
@ -162,12 +162,12 @@ void ModifiersTest::assertModifierEquals(const Modifier &mod, NumberStringBuilde
|
||||
UErrorCode &status) {
|
||||
int32_t oldCount = sb.codePointCount();
|
||||
mod.apply(sb, 0, sb.length(), status);
|
||||
assertEquals("Prefix length", expectedPrefixLength, mod.getPrefixLength(status));
|
||||
assertEquals("Prefix length", expectedPrefixLength, mod.getPrefixLength());
|
||||
assertEquals("Strong", expectedStrong, mod.isStrong());
|
||||
if (dynamic_cast<const CurrencySpacingEnabledModifier*>(&mod) == nullptr) {
|
||||
// i.e., if mod is not a CurrencySpacingEnabledModifier
|
||||
assertEquals("Code point count equals actual code point count",
|
||||
sb.codePointCount() - oldCount, mod.getCodePointCount(status));
|
||||
sb.codePointCount() - oldCount, mod.getCodePointCount());
|
||||
}
|
||||
|
||||
UnicodeString debugString;
|
||||
|
@ -170,14 +170,14 @@ void PatternModifierTest::testMutableEqualsImmutable() {
|
||||
UnicodeString PatternModifierTest::getPrefix(const MutablePatternModifier &mod, UErrorCode &status) {
|
||||
NumberStringBuilder nsb;
|
||||
mod.apply(nsb, 0, 0, status);
|
||||
int32_t prefixLength = mod.getPrefixLength(status);
|
||||
int32_t prefixLength = mod.getPrefixLength();
|
||||
return UnicodeString(nsb.toUnicodeString(), 0, prefixLength);
|
||||
}
|
||||
|
||||
UnicodeString PatternModifierTest::getSuffix(const MutablePatternModifier &mod, UErrorCode &status) {
|
||||
NumberStringBuilder nsb;
|
||||
mod.apply(nsb, 0, 0, status);
|
||||
int32_t prefixLength = mod.getPrefixLength(status);
|
||||
int32_t prefixLength = mod.getPrefixLength();
|
||||
return UnicodeString(nsb.toUnicodeString(), prefixLength, nsb.length() - prefixLength);
|
||||
}
|
||||
|
||||
|
793
icu4c/source/test/intltest/numbertest_range.cpp
Normal file
793
icu4c/source/test/intltest/numbertest_range.cpp
Normal file
@ -0,0 +1,793 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "numbertest.h"
|
||||
#include "unicode/numberrangeformatter.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <numparse_affixes.h>
|
||||
|
||||
// Horrible workaround for the lack of a status code in the constructor...
|
||||
// (Also affects numbertest_api.cpp)
|
||||
UErrorCode globalNumberRangeFormatterTestStatus = U_ZERO_ERROR;
|
||||
|
||||
NumberRangeFormatterTest::NumberRangeFormatterTest()
|
||||
: NumberRangeFormatterTest(globalNumberRangeFormatterTestStatus) {
|
||||
}
|
||||
|
||||
NumberRangeFormatterTest::NumberRangeFormatterTest(UErrorCode& status)
|
||||
: USD(u"USD", status),
|
||||
GBP(u"GBP", status),
|
||||
PTE(u"PTE", status) {
|
||||
|
||||
// Check for error on the first MeasureUnit in case there is no data
|
||||
LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
|
||||
if (U_FAILURE(status)) {
|
||||
dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
|
||||
return;
|
||||
}
|
||||
METER = *unit;
|
||||
|
||||
KILOMETER = *LocalPointer<MeasureUnit>(MeasureUnit::createKilometer(status));
|
||||
FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
|
||||
KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
|
||||
if (exec) {
|
||||
logln("TestSuite NumberRangeFormatterTest: ");
|
||||
}
|
||||
TESTCASE_AUTO_BEGIN;
|
||||
TESTCASE_AUTO(testSanity);
|
||||
TESTCASE_AUTO(testBasic);
|
||||
TESTCASE_AUTO(testCollapse);
|
||||
TESTCASE_AUTO(testIdentity);
|
||||
TESTCASE_AUTO(testDifferentFormatters);
|
||||
TESTCASE_AUTO(testPlurals);
|
||||
TESTCASE_AUTO(testCopyMove);
|
||||
TESTCASE_AUTO_END;
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testSanity() {
|
||||
IcuTestErrorCode status(*this, "testSanity");
|
||||
LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us");
|
||||
LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us");
|
||||
assertEquals("Formatters should have same behavior 1",
|
||||
lnrf1.formatFormattableRange(4, 6, status).toString(status),
|
||||
lnrf2.formatFormattableRange(4, 6, status).toString(status));
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testBasic() {
|
||||
assertFormatRange(
|
||||
u"Basic",
|
||||
NumberRangeFormatter::with(),
|
||||
Locale("en-us"),
|
||||
u"1–5",
|
||||
u"~5",
|
||||
u"~5",
|
||||
u"0–3",
|
||||
u"~0",
|
||||
u"3–3,000",
|
||||
u"3,000–5,000",
|
||||
u"4,999–5,001",
|
||||
u"~5,000",
|
||||
u"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Basic with units",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3,000 m",
|
||||
u"3,000–5,000 m",
|
||||
u"4,999–5,001 m",
|
||||
u"~5,000 m",
|
||||
u"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
u"Basic with different units",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterFirst(NumberFormatter::with().unit(METER))
|
||||
.numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)),
|
||||
Locale("en-us"),
|
||||
u"1 m – 5 km",
|
||||
u"5 m – 5 km",
|
||||
u"5 m – 5 km",
|
||||
u"0 m – 3 km",
|
||||
u"0 m – 0 km",
|
||||
u"3 m – 3,000 km",
|
||||
u"3,000 m – 5,000 km",
|
||||
u"4,999 m – 5,001 km",
|
||||
u"5,000 m – 5,000 km",
|
||||
u"5,000 m – 5,000,000 km");
|
||||
|
||||
assertFormatRange(
|
||||
u"Basic long unit",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
|
||||
Locale("en-us"),
|
||||
u"1–5 meters",
|
||||
u"~5 meters",
|
||||
u"~5 meters",
|
||||
u"0–3 meters",
|
||||
u"~0 meters",
|
||||
u"3–3,000 meters",
|
||||
u"3,000–5,000 meters",
|
||||
u"4,999–5,001 meters",
|
||||
u"~5,000 meters",
|
||||
u"5,000–5,000,000 meters");
|
||||
|
||||
assertFormatRange(
|
||||
u"Non-English locale and unit",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
|
||||
Locale("fr-FR"),
|
||||
u"1–5 degrés Fahrenheit",
|
||||
u"~5 degrés Fahrenheit",
|
||||
u"~5 degrés Fahrenheit",
|
||||
u"0–3 degrés Fahrenheit",
|
||||
u"~0 degré Fahrenheit",
|
||||
u"3–3 000 degrés Fahrenheit",
|
||||
u"3 000–5 000 degrés Fahrenheit",
|
||||
u"4 999–5 001 degrés Fahrenheit",
|
||||
u"~5 000 degrés Fahrenheit",
|
||||
u"5 000–5 000 000 degrés Fahrenheit");
|
||||
|
||||
assertFormatRange(
|
||||
u"Locale with custom range separator",
|
||||
NumberRangeFormatter::with(),
|
||||
Locale("ja"),
|
||||
u"1~5",
|
||||
u"~5",
|
||||
u"~5",
|
||||
u"0~3",
|
||||
u"~0",
|
||||
u"3~3,000",
|
||||
u"3,000~5,000",
|
||||
u"4,999~5,001",
|
||||
u"~5,000",
|
||||
u"5,000~5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Locale that already has spaces around range separator",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(KELVIN)),
|
||||
Locale("hr"),
|
||||
u"1 K – 5 K",
|
||||
u"~5 K",
|
||||
u"~5 K",
|
||||
u"0 K – 3 K",
|
||||
u"~0 K",
|
||||
u"3 K – 3.000 K",
|
||||
u"3.000 K – 5.000 K",
|
||||
u"4.999 K – 5.001 K",
|
||||
u"~5.000 K",
|
||||
u"5.000 K – 5.000.000 K");
|
||||
|
||||
assertFormatRange(
|
||||
u"Locale with custom numbering system and no plural ranges data",
|
||||
NumberRangeFormatter::with(),
|
||||
Locale("shn@numbers=beng"),
|
||||
// 012459 = ০১৩৪৫৯
|
||||
u"১–৫",
|
||||
u"~৫",
|
||||
u"~৫",
|
||||
u"০–৩",
|
||||
u"~০",
|
||||
u"৩–৩,০০০",
|
||||
u"৩,০০০–৫,০০০",
|
||||
u"৪,৯৯৯–৫,০০১",
|
||||
u"~৫,০০০",
|
||||
u"৫,০০০–৫,০০০,০০০");
|
||||
|
||||
assertFormatRange(
|
||||
u"Portuguese currency",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(PTE)),
|
||||
Locale("pt-PT"),
|
||||
u"1$00 - 5$00 \u200B",
|
||||
u"~5$00 \u200B",
|
||||
u"~5$00 \u200B",
|
||||
u"0$00 - 3$00 \u200B",
|
||||
u"~0$00 \u200B",
|
||||
u"3$00 - 3000$00 \u200B",
|
||||
u"3000$00 - 5000$00 \u200B",
|
||||
u"4999$00 - 5001$00 \u200B",
|
||||
u"~5000$00 \u200B",
|
||||
u"5000$00 - 5,000,000$00 \u200B");
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testCollapse() {
|
||||
assertFormatRange(
|
||||
u"Default collapse on currency (default rounding)",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD)),
|
||||
Locale("en-us"),
|
||||
u"$1.00 – $5.00",
|
||||
u"~$5.00",
|
||||
u"~$5.00",
|
||||
u"$0.00 – $3.00",
|
||||
u"~$0.00",
|
||||
u"$3.00 – $3,000.00",
|
||||
u"$3,000.00 – $5,000.00",
|
||||
u"$4,999.00 – $5,001.00",
|
||||
u"~$5,000.00",
|
||||
u"$5,000.00 – $5,000,000.00");
|
||||
|
||||
assertFormatRange(
|
||||
u"Default collapse on currency",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"$1 – $5",
|
||||
u"~$5",
|
||||
u"~$5",
|
||||
u"$0 – $3",
|
||||
u"~$0",
|
||||
u"$3 – $3,000",
|
||||
u"$3,000 – $5,000",
|
||||
u"$4,999 – $5,001",
|
||||
u"~$5,000",
|
||||
u"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on currency",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"$1 – $5",
|
||||
u"~$5",
|
||||
u"~$5",
|
||||
u"$0 – $3",
|
||||
u"~$0",
|
||||
u"$3 – $3,000",
|
||||
u"$3,000 – $5,000",
|
||||
u"$4,999 – $5,001",
|
||||
u"~$5,000",
|
||||
u"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Unit collapse on currency",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"$1–5",
|
||||
u"~$5",
|
||||
u"~$5",
|
||||
u"$0–3",
|
||||
u"~$0",
|
||||
u"$3–3,000",
|
||||
u"$3,000–5,000",
|
||||
u"$4,999–5,001",
|
||||
u"~$5,000",
|
||||
u"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on currency",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"$1–5",
|
||||
u"~$5",
|
||||
u"~$5",
|
||||
u"$0–3",
|
||||
u"~$0",
|
||||
u"$3–3,000",
|
||||
u"$3,000–5,000",
|
||||
u"$4,999–5,001",
|
||||
u"~$5,000",
|
||||
u"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Default collapse on currency ISO code",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
|
||||
.precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"GBP 1–5",
|
||||
u"~GBP 5", // TODO: Fix this at some point
|
||||
u"~GBP 5",
|
||||
u"GBP 0–3",
|
||||
u"~GBP 0",
|
||||
u"GBP 3–3,000",
|
||||
u"GBP 3,000–5,000",
|
||||
u"GBP 4,999–5,001",
|
||||
u"~GBP 5,000",
|
||||
u"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on currency ISO code",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
|
||||
.precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"GBP 1 – GBP 5",
|
||||
u"~GBP 5", // TODO: Fix this at some point
|
||||
u"~GBP 5",
|
||||
u"GBP 0 – GBP 3",
|
||||
u"~GBP 0",
|
||||
u"GBP 3 – GBP 3,000",
|
||||
u"GBP 3,000 – GBP 5,000",
|
||||
u"GBP 4,999 – GBP 5,001",
|
||||
u"~GBP 5,000",
|
||||
u"GBP 5,000 – GBP 5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Unit collapse on currency ISO code",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
|
||||
.numberFormatterBoth(NumberFormatter::with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
|
||||
.precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"GBP 1–5",
|
||||
u"~GBP 5", // TODO: Fix this at some point
|
||||
u"~GBP 5",
|
||||
u"GBP 0–3",
|
||||
u"~GBP 0",
|
||||
u"GBP 3–3,000",
|
||||
u"GBP 3,000–5,000",
|
||||
u"GBP 4,999–5,001",
|
||||
u"~GBP 5,000",
|
||||
u"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on currency ISO code",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
|
||||
.precision(Precision::integer())),
|
||||
Locale("en-us"),
|
||||
u"GBP 1–5",
|
||||
u"~GBP 5", // TODO: Fix this at some point
|
||||
u"~GBP 5",
|
||||
u"GBP 0–3",
|
||||
u"~GBP 0",
|
||||
u"GBP 3–3,000",
|
||||
u"GBP 3,000–5,000",
|
||||
u"GBP 4,999–5,001",
|
||||
u"~GBP 5,000",
|
||||
u"GBP 5,000–5,000,000");
|
||||
|
||||
// Default collapse on measurement unit is in testBasic()
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on measurement unit",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1 m – 5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0 m – 3 m",
|
||||
u"~0 m",
|
||||
u"3 m – 3,000 m",
|
||||
u"3,000 m – 5,000 m",
|
||||
u"4,999 m – 5,001 m",
|
||||
u"~5,000 m",
|
||||
u"5,000 m – 5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
u"Unit collapse on measurement unit",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3,000 m",
|
||||
u"3,000–5,000 m",
|
||||
u"4,999–5,001 m",
|
||||
u"~5,000 m",
|
||||
u"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on measurement unit",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3,000 m",
|
||||
u"3,000–5,000 m",
|
||||
u"4,999–5,001 m",
|
||||
u"~5,000 m",
|
||||
u"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
u"Default collapse, long-form compact notation",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
|
||||
Locale("de-CH"),
|
||||
u"1–5",
|
||||
u"~5",
|
||||
u"~5",
|
||||
u"0–3",
|
||||
u"~0",
|
||||
u"3–3 Tausend",
|
||||
u"3–5 Tausend",
|
||||
u"~5 Tausend",
|
||||
u"~5 Tausend",
|
||||
u"5 Tausend – 5 Millionen");
|
||||
|
||||
assertFormatRange(
|
||||
u"Unit collapse, long-form compact notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
|
||||
Locale("de-CH"),
|
||||
u"1–5",
|
||||
u"~5",
|
||||
u"~5",
|
||||
u"0–3",
|
||||
u"~0",
|
||||
u"3–3 Tausend",
|
||||
u"3 Tausend – 5 Tausend",
|
||||
u"~5 Tausend",
|
||||
u"~5 Tausend",
|
||||
u"5 Tausend – 5 Millionen");
|
||||
|
||||
assertFormatRange(
|
||||
u"Default collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3K m",
|
||||
u"3K – 5K m",
|
||||
u"~5K m",
|
||||
u"~5K m",
|
||||
u"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1 m – 5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0 m – 3 m",
|
||||
u"~0 m",
|
||||
u"3 m – 3K m",
|
||||
u"3K m – 5K m",
|
||||
u"~5K m",
|
||||
u"~5K m",
|
||||
u"5K m – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
u"Unit collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3K m",
|
||||
u"3K – 5K m",
|
||||
u"~5K m",
|
||||
u"~5K m",
|
||||
u"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
|
||||
Locale("en-us"),
|
||||
u"1–5 m",
|
||||
u"~5 m",
|
||||
u"~5 m",
|
||||
u"0–3 m",
|
||||
u"~0 m",
|
||||
u"3–3K m",
|
||||
u"3–5K m", // this one is the key use case for ALL
|
||||
u"~5K m",
|
||||
u"~5K m",
|
||||
u"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on scientific notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
|
||||
Locale("en-us"),
|
||||
u"1E0 – 5E0",
|
||||
u"~5E0",
|
||||
u"~5E0",
|
||||
u"0E0 – 3E0",
|
||||
u"~0E0",
|
||||
u"3E0 – 3E3",
|
||||
u"3E3 – 5E3",
|
||||
u"4.999E3 – 5.001E3",
|
||||
u"~5E3",
|
||||
u"5E3 – 5E6");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on scientific notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
|
||||
Locale("en-us"),
|
||||
u"1–5E0",
|
||||
u"~5E0",
|
||||
u"~5E0",
|
||||
u"0–3E0",
|
||||
u"~0E0",
|
||||
u"3E0 – 3E3",
|
||||
u"3–5E3",
|
||||
u"4.999–5.001E3",
|
||||
u"~5E3",
|
||||
u"5E3 – 5E6");
|
||||
|
||||
// TODO: Test compact currency?
|
||||
// The code is not smart enough to differentiate the notation from the unit.
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testIdentity() {
|
||||
assertFormatRange(
|
||||
u"Identity fallback Range",
|
||||
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
|
||||
Locale("en-us"),
|
||||
u"1–5",
|
||||
u"5–5",
|
||||
u"5–5",
|
||||
u"0–3",
|
||||
u"0–0",
|
||||
u"3–3,000",
|
||||
u"3,000–5,000",
|
||||
u"4,999–5,001",
|
||||
u"5,000–5,000",
|
||||
u"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Identity fallback Approximately or Single Value",
|
||||
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
|
||||
Locale("en-us"),
|
||||
u"1–5",
|
||||
u"~5",
|
||||
u"5",
|
||||
u"0–3",
|
||||
u"0",
|
||||
u"3–3,000",
|
||||
u"3,000–5,000",
|
||||
u"4,999–5,001",
|
||||
u"5,000",
|
||||
u"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Identity fallback Single Value",
|
||||
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
|
||||
Locale("en-us"),
|
||||
u"1–5",
|
||||
u"5",
|
||||
u"5",
|
||||
u"0–3",
|
||||
u"0",
|
||||
u"3–3,000",
|
||||
u"3,000–5,000",
|
||||
u"4,999–5,001",
|
||||
u"5,000",
|
||||
u"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
u"Identity fallback Approximately or Single Value with compact notation",
|
||||
NumberRangeFormatter::with()
|
||||
.identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
|
||||
Locale("en-us"),
|
||||
u"1–5",
|
||||
u"~5",
|
||||
u"5",
|
||||
u"0–3",
|
||||
u"0",
|
||||
u"3–3K",
|
||||
u"3K – 5K",
|
||||
u"~5K",
|
||||
u"5K",
|
||||
u"5K – 5M");
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testDifferentFormatters() {
|
||||
assertFormatRange(
|
||||
u"Different rounding rules",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
|
||||
.numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedDigits(2))),
|
||||
Locale("en-us"),
|
||||
u"1–5.0",
|
||||
u"5–5.0",
|
||||
u"5–5.0",
|
||||
u"0–3.0",
|
||||
u"0–0.0",
|
||||
u"3–3,000",
|
||||
u"3,000–5,000",
|
||||
u"4,999–5,000",
|
||||
u"5,000–5,000", // TODO: Should this one be ~5,000?
|
||||
u"5,000–5,000,000");
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testPlurals() {
|
||||
IcuTestErrorCode status(*this, "testPlurals");
|
||||
|
||||
// Locale sl has interesting plural forms:
|
||||
// GBP{
|
||||
// one{"britanski funt"}
|
||||
// two{"britanska funta"}
|
||||
// few{"britanski funti"}
|
||||
// other{"britanskih funtov"}
|
||||
// }
|
||||
Locale locale("sl");
|
||||
|
||||
UnlocalizedNumberFormatter unf = NumberFormatter::with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
|
||||
.precision(Precision::integer());
|
||||
LocalizedNumberFormatter lnf = unf.locale(locale);
|
||||
|
||||
// For comparison, run the non-range version of the formatter
|
||||
assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status));
|
||||
assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status));
|
||||
assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status));
|
||||
assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status));
|
||||
if (status.errIfFailureAndReset()) { return; }
|
||||
|
||||
LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(unf)
|
||||
.identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
|
||||
.locale(locale);
|
||||
|
||||
struct TestCase {
|
||||
double first;
|
||||
double second;
|
||||
const char16_t* expected;
|
||||
} cases[] = {
|
||||
{1, 1, u"1–1 britanski funti"}, // one + one -> few
|
||||
{1, 2, u"1–2 britanska funta"}, // one + two -> two
|
||||
{1, 3, u"1–3 britanski funti"}, // one + few -> few
|
||||
{1, 5, u"1–5 britanskih funtov"}, // one + other -> other
|
||||
{2, 1, u"2–1 britanski funti"}, // two + one -> few
|
||||
{2, 2, u"2–2 britanska funta"}, // two + two -> two
|
||||
{2, 3, u"2–3 britanski funti"}, // two + few -> few
|
||||
{2, 5, u"2–5 britanskih funtov"}, // two + other -> other
|
||||
{3, 1, u"3–1 britanski funti"}, // few + one -> few
|
||||
{3, 2, u"3–2 britanska funta"}, // few + two -> two
|
||||
{3, 3, u"3–3 britanski funti"}, // few + few -> few
|
||||
{3, 5, u"3–5 britanskih funtov"}, // few + other -> other
|
||||
{5, 1, u"5–1 britanski funti"}, // other + one -> few
|
||||
{5, 2, u"5–2 britanska funta"}, // other + two -> two
|
||||
{5, 3, u"5–3 britanski funti"}, // other + few -> few
|
||||
{5, 5, u"5–5 britanskih funtov"}, // other + other -> other
|
||||
};
|
||||
for (auto& cas : cases) {
|
||||
UnicodeString message = Int64ToUnicodeString(cas.first);
|
||||
message += u" ";
|
||||
message += Int64ToUnicodeString(cas.second);
|
||||
status.setScope(message);
|
||||
UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status);
|
||||
assertEquals(message, cas.expected, actual);
|
||||
status.errIfFailureAndReset();
|
||||
}
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testCopyMove() {
|
||||
IcuTestErrorCode status(*this, "testCopyMove");
|
||||
|
||||
// Default constructors
|
||||
LocalizedNumberRangeFormatter l1;
|
||||
assertEquals("Initial behavior", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
|
||||
if (status.errDataIfFailureAndReset()) { return; }
|
||||
|
||||
// Setup
|
||||
l1 = NumberRangeFormatter::withLocale("fr-FR")
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(USD));
|
||||
assertEquals("Currency behavior", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// Copy constructor
|
||||
LocalizedNumberRangeFormatter l2 = l1;
|
||||
assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// Move constructor
|
||||
LocalizedNumberRangeFormatter l3 = std::move(l1);
|
||||
assertEquals("Move constructor", u"1,00–5,00 $US", l3.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// Reset objects for assignment tests
|
||||
l1 = NumberRangeFormatter::withLocale("en-us");
|
||||
l2 = NumberRangeFormatter::withLocale("en-us");
|
||||
assertEquals("Rest behavior, l1", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
|
||||
assertEquals("Rest behavior, l2", u"1–5", l2.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// Copy assignment
|
||||
l1 = l3;
|
||||
assertEquals("Copy constructor", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// Move assignment
|
||||
l2 = std::move(l3);
|
||||
assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
|
||||
|
||||
// FormattedNumberRange
|
||||
FormattedNumberRange result = l1.formatFormattableRange(1, 5, status);
|
||||
assertEquals("FormattedNumberRange move constructor", u"1,00–5,00 $US", result.toString(status));
|
||||
result = l1.formatFormattableRange(3, 6, status);
|
||||
assertEquals("FormattedNumberRange move assignment", u"3,00–6,00 $US", result.toString(status));
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::assertFormatRange(
|
||||
const char16_t* message,
|
||||
const UnlocalizedNumberRangeFormatter& f,
|
||||
Locale locale,
|
||||
const char16_t* expected_10_50,
|
||||
const char16_t* expected_49_51,
|
||||
const char16_t* expected_50_50,
|
||||
const char16_t* expected_00_30,
|
||||
const char16_t* expected_00_00,
|
||||
const char16_t* expected_30_3K,
|
||||
const char16_t* expected_30K_50K,
|
||||
const char16_t* expected_49K_51K,
|
||||
const char16_t* expected_50K_50K,
|
||||
const char16_t* expected_50K_50M) {
|
||||
LocalizedNumberRangeFormatter l = f.locale(locale);
|
||||
assertFormattedRangeEquals(message, l, 1, 5, expected_10_50);
|
||||
assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51);
|
||||
assertFormattedRangeEquals(message, l, 5, 5, expected_50_50);
|
||||
assertFormattedRangeEquals(message, l, 0, 3, expected_00_30);
|
||||
assertFormattedRangeEquals(message, l, 0, 0, expected_00_00);
|
||||
assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K);
|
||||
assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K);
|
||||
assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K);
|
||||
assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K);
|
||||
assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M);
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::assertFormattedRangeEquals(
|
||||
const char16_t* message,
|
||||
const LocalizedNumberRangeFormatter& l,
|
||||
double first,
|
||||
double second,
|
||||
const char16_t* expected) {
|
||||
IcuTestErrorCode status(*this, "assertFormattedRangeEquals");
|
||||
UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second);
|
||||
status.setScope(fullMessage);
|
||||
UnicodeString actual = l.formatFormattableRange(first, second, status).toString(status);
|
||||
assertEquals(fullMessage, expected, actual);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
@ -215,6 +215,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
|
||||
TESTCASE_AUTO(Test13763_FieldPositionIteratorOffset);
|
||||
TESTCASE_AUTO(Test13777_ParseLongNameNonCurrencyMode);
|
||||
TESTCASE_AUTO(Test13804_EmptyStringsWhenParsing);
|
||||
TESTCASE_AUTO(Test20037_ScientificIntegerOverflow);
|
||||
TESTCASE_AUTO(Test13840_ParseLongStringCrash);
|
||||
TESTCASE_AUTO(Test13850_EmptyStringCurrency);
|
||||
TESTCASE_AUTO_END;
|
||||
@ -7367,6 +7368,35 @@ void NumberFormatTest::TestSignificantDigits(void) {
|
||||
}
|
||||
result.remove();
|
||||
}
|
||||
|
||||
// Test for ICU-20063
|
||||
{
|
||||
DecimalFormat df({"en-us", status}, status);
|
||||
df.setSignificantDigitsUsed(TRUE);
|
||||
expect(df, 9.87654321, u"9.87654");
|
||||
df.setMaximumSignificantDigits(3);
|
||||
expect(df, 9.87654321, u"9.88");
|
||||
// setSignificantDigitsUsed with maxSig only
|
||||
df.setSignificantDigitsUsed(TRUE);
|
||||
expect(df, 9.87654321, u"9.88");
|
||||
df.setMinimumSignificantDigits(2);
|
||||
expect(df, 9, u"9.0");
|
||||
// setSignificantDigitsUsed with both minSig and maxSig
|
||||
df.setSignificantDigitsUsed(TRUE);
|
||||
expect(df, 9, u"9.0");
|
||||
// setSignificantDigitsUsed to false: should revert to fraction rounding
|
||||
df.setSignificantDigitsUsed(FALSE);
|
||||
expect(df, 9.87654321, u"9.876543");
|
||||
expect(df, 9, u"9");
|
||||
df.setSignificantDigitsUsed(TRUE);
|
||||
df.setMinimumSignificantDigits(2);
|
||||
expect(df, 9.87654321, u"9.87654");
|
||||
expect(df, 9, u"9.0");
|
||||
// setSignificantDigitsUsed with minSig only
|
||||
df.setSignificantDigitsUsed(TRUE);
|
||||
expect(df, 9.87654321, u"9.87654");
|
||||
expect(df, 9, u"9.0");
|
||||
}
|
||||
}
|
||||
|
||||
void NumberFormatTest::TestShowZero() {
|
||||
@ -9156,6 +9186,28 @@ void NumberFormatTest::Test13804_EmptyStringsWhenParsing() {
|
||||
}
|
||||
}
|
||||
|
||||
void NumberFormatTest::Test20037_ScientificIntegerOverflow() {
|
||||
IcuTestErrorCode status(*this, "Test20037_ScientificIntegerOverflow");
|
||||
|
||||
LocalPointer<NumberFormat> nf(NumberFormat::createInstance(status));
|
||||
Formattable result;
|
||||
|
||||
// Test overflow of exponent
|
||||
nf->parse(u"1E-2147483648", result, status);
|
||||
StringPiece sp = result.getDecimalNumber(status);
|
||||
assertEquals(u"Should snap to zero",
|
||||
u"0",
|
||||
{sp.data(), sp.length(), US_INV});
|
||||
|
||||
// Test edge case overflow of exponent
|
||||
result = Formattable();
|
||||
nf->parse(u"1E-2147483647E-1", result, status);
|
||||
sp = result.getDecimalNumber(status);
|
||||
assertEquals(u"Should not overflow and should parse only the first exponent",
|
||||
u"1E-2147483647",
|
||||
{sp.data(), sp.length(), US_INV});
|
||||
}
|
||||
|
||||
void NumberFormatTest::Test13840_ParseLongStringCrash() {
|
||||
IcuTestErrorCode status(*this, "Test13840_ParseLongStringCrash");
|
||||
|
||||
|
@ -279,6 +279,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
|
||||
void Test13763_FieldPositionIteratorOffset();
|
||||
void Test13777_ParseLongNameNonCurrencyMode();
|
||||
void Test13804_EmptyStringsWhenParsing();
|
||||
void Test20037_ScientificIntegerOverflow();
|
||||
void Test13840_ParseLongStringCrash();
|
||||
void Test13850_EmptyStringCurrency();
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "umutex.h"
|
||||
#include "cmemory.h"
|
||||
#include "cstring.h"
|
||||
#include "indiancal.h"
|
||||
#include "uparse.h"
|
||||
#include "unicode/localpointer.h"
|
||||
#include "unicode/resbund.h"
|
||||
@ -82,6 +83,7 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec,
|
||||
TESTCASE_AUTO(TestBreakTranslit);
|
||||
TESTCASE_AUTO(TestIncDec);
|
||||
#endif /* #if !UCONFIG_NO_TRANSLITERATION */
|
||||
TESTCASE_AUTO(Test20104);
|
||||
TESTCASE_AUTO_END
|
||||
}
|
||||
|
||||
@ -1549,4 +1551,35 @@ void MultithreadTest::TestIncDec()
|
||||
}
|
||||
|
||||
|
||||
static Calendar *gSharedCalendar = {};
|
||||
|
||||
class Test20104Thread : public SimpleThread {
|
||||
public:
|
||||
Test20104Thread() { };
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
void Test20104Thread::run() {
|
||||
gSharedCalendar->defaultCenturyStartYear();
|
||||
}
|
||||
|
||||
void MultithreadTest::Test20104() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
Locale loc("hi_IN");
|
||||
gSharedCalendar = new IndianCalendar(loc, status);
|
||||
assertSuccess("Test20104", status);
|
||||
|
||||
static constexpr int NUM_THREADS = 4;
|
||||
Test20104Thread threads[NUM_THREADS];
|
||||
for (auto &thread:threads) {
|
||||
thread.start();
|
||||
}
|
||||
for (auto &thread:threads) {
|
||||
thread.join();
|
||||
}
|
||||
delete gSharedCalendar;
|
||||
// Note: failure is reported by Thread Sanitizer. Test itself succeeds.
|
||||
}
|
||||
|
||||
|
||||
#endif /* !UCONFIG_NO_TRANSLITERATION */
|
||||
|
@ -53,6 +53,7 @@ public:
|
||||
void TestUnifiedCache();
|
||||
void TestBreakTranslit();
|
||||
void TestIncDec();
|
||||
void Test20104();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -353,6 +353,15 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre
|
||||
// JDK fails here because it tries to use 9 + 6 = 15 sig digits.
|
||||
2 9 1 6 29.979246E7 K
|
||||
|
||||
test ticket 20058
|
||||
set locale en
|
||||
begin
|
||||
pattern format output breaks
|
||||
#00.0##E0 0 0.0E0 K
|
||||
#00.0##E0 1.2 1.2E0 K
|
||||
#00.0E0 0 0.0E0 K
|
||||
#00.0E0 1.2 1.2E0 K
|
||||
|
||||
test significant digits scientific
|
||||
set locale en
|
||||
set pattern #E0
|
||||
|
104
icu4c/source/tools/icu-file-utf8-check.py
Executable file
104
icu4c/source/tools/icu-file-utf8-check.py
Executable file
@ -0,0 +1,104 @@
|
||||
#! /usr/bin/python -B
|
||||
|
||||
# Copyright (C) 2016 and later: Unicode, Inc. and others.
|
||||
# License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
# Copyright (C) 2009-2011, International Business Machines Corporation, Google and Others.
|
||||
# All rights reserved.
|
||||
|
||||
#
|
||||
# Script to check that ICU source files contain only valid UTF-8 encoded text,
|
||||
# and that all files except '.txt' files do not contain a Byte Order Mark (BOM).
|
||||
#
|
||||
# THIS SCRIPT DOES NOT WORK ON WINDOWS
|
||||
# It only works correctly on platforms where the native line ending is a plain \n
|
||||
#
|
||||
# usage:
|
||||
# icu-file-utf8-check.py [options]
|
||||
#
|
||||
# options:
|
||||
# -h | --help Print a usage line and exit.
|
||||
#
|
||||
# The tool operates recursively on the directory from which it is run.
|
||||
# Only files from the ICU github repository are checked.
|
||||
# No changes are made to the repository; only the working copy will be altered.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import getopt
|
||||
|
||||
|
||||
def runCommand(cmd):
|
||||
output_file = os.popen(cmd);
|
||||
output_text = output_file.read();
|
||||
exit_status = output_file.close();
|
||||
if exit_status:
|
||||
print >>sys.stderr, '"', cmd, '" failed. Exiting.'
|
||||
sys.exit(exit_status)
|
||||
return output_text
|
||||
|
||||
|
||||
def usage():
|
||||
print "usage: " + sys.argv[0] + " [-h | --help]"
|
||||
|
||||
|
||||
#
|
||||
# File check. Check source code files for UTF-8 and all except text files for not containing a BOM
|
||||
# file_name: name of a text file.
|
||||
# is_source: Flag, set to True if file is a source code file (.c, .cpp, .h, .java).
|
||||
#
|
||||
def check_file(file_name, is_source):
|
||||
f = open(file_name, 'r')
|
||||
bytes = f.read()
|
||||
f.close()
|
||||
|
||||
if is_source:
|
||||
try:
|
||||
bytes.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
print "Error: %s is a source code file but contains non-utf-8 bytes." % file_name
|
||||
|
||||
if ord(bytes[0]) == 0xef:
|
||||
if not (file_name.endswith(".txt") or file_name.endswith(".sln")
|
||||
or file_name.endswith(".targets")
|
||||
or ".vcxproj" in file_name):
|
||||
print "Warning: file %s contains a UTF-8 BOM: " % file_name
|
||||
|
||||
return
|
||||
|
||||
def main(argv):
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "h", ("help"))
|
||||
except getopt.GetoptError:
|
||||
print "unrecognized option: " + argv[0]
|
||||
usage()
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
usage()
|
||||
sys.exit()
|
||||
if args:
|
||||
print "unexpected command line argument"
|
||||
usage()
|
||||
sys.exit()
|
||||
|
||||
output = runCommand("git ls-files ");
|
||||
file_list = output.splitlines()
|
||||
|
||||
source_file_re = re.compile(".*((?:\\.c$)|(?:\\.cpp$)|(?:\\.h$)|(?:\\.java$))")
|
||||
|
||||
for f in file_list:
|
||||
if os.path.isdir(f):
|
||||
print "Skipping dir " + f
|
||||
continue
|
||||
if not os.path.isfile(f):
|
||||
print "Repository file not in working copy: " + f
|
||||
continue;
|
||||
|
||||
source_file = source_file_re.match(f)
|
||||
check_file(f, source_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
@ -1,189 +0,0 @@
|
||||
#! /usr/bin/python -B
|
||||
|
||||
# Copyright (C) 2016 and later: Unicode, Inc. and others.
|
||||
# License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
# Copyright (C) 2009-2011, International Business Machines Corporation, Google and Others.
|
||||
# All rights reserved.
|
||||
|
||||
#
|
||||
# Script to check and fix svn property settings for ICU source files.
|
||||
# Also check for the correct line endings on files with svn:eol-style = native
|
||||
#
|
||||
# THIS SCRIPT DOES NOT WORK ON WINDOWS
|
||||
# It only works correctly on platforms where the native line ending is a plain \n
|
||||
#
|
||||
# usage:
|
||||
# icu-svnprops-check.py [options]
|
||||
#
|
||||
# options:
|
||||
# -f | --fix Fix any problems that are found
|
||||
# -h | --help Print a usage line and exit.
|
||||
#
|
||||
# The tool operates recursively on the directory from which it is run.
|
||||
# Only files from the svn repository are checked.
|
||||
# No changes are made to the repository; only the working copy will be altered.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import getopt
|
||||
|
||||
|
||||
# file_types: The parsed form of the svn auto-props specification.
|
||||
# A list of file types - .cc, .cpp, .txt, etc.
|
||||
# each element is a [type, proplist]
|
||||
# "type" is a regular expression string that will match a file name
|
||||
# prop list is another list, one element per property.
|
||||
# Each property item is a two element list, [prop name, prop value]
|
||||
file_types = list()
|
||||
|
||||
def parse_auto_props():
|
||||
aprops = svn_auto_props.splitlines()
|
||||
for propline in aprops:
|
||||
if re.match("\s*(#.*)?$", propline): # Match comment and blank lines
|
||||
continue
|
||||
if re.match("\s*\[auto-props\]", propline): # Match the [auto-props] line.
|
||||
continue
|
||||
if not re.match("\s*[^\s]+\s*=", propline): # minimal syntax check for <file-type> =
|
||||
print "Bad line from autoprops definitions: " + propline
|
||||
continue
|
||||
file_type, string_proplist = propline.split("=", 1)
|
||||
|
||||
#transform the file type expression from autoprops into a normal regular expression.
|
||||
# e.g. "*.cpp" ==> ".*\.cpp$"
|
||||
file_type = file_type.strip()
|
||||
file_type = file_type.replace(".", "\.")
|
||||
file_type = file_type.replace("*", ".*")
|
||||
file_type = file_type + "$"
|
||||
|
||||
# example string_proplist at this point: " svn:eol-style=native;svn:executable"
|
||||
# split on ';' into a list of properties. The negative lookahead and lookbehind
|
||||
# in the split regexp are to prevent matching on ';;', which is an escaped ';'
|
||||
# within a property value.
|
||||
string_proplist = re.split("(?<!;);(?!;)", string_proplist)
|
||||
proplist = list()
|
||||
for prop in string_proplist:
|
||||
if prop.find("=") >= 0:
|
||||
prop_name, prop_val = prop.split("=", 1)
|
||||
else:
|
||||
# properties with no explicit value, e.g. svn:executable
|
||||
prop_name, prop_val = prop, ""
|
||||
prop_name = prop_name.strip()
|
||||
prop_val = prop_val.strip()
|
||||
# unescape any ";;" in a property value, e.g. the mime-type from
|
||||
# *.java = svn:eol-style=native;svn:mime-type=text/plain;;charset=utf-8
|
||||
prop_val = prop_val.replace(";;", ";");
|
||||
# If the prop value "is quoted", remove the quotes.
|
||||
# See svn:keywords for an example of a quoted prop value.
|
||||
match = re.match('^"(.+)"$', prop_val)
|
||||
if match:
|
||||
prop_val = match.group(1)
|
||||
proplist.append((prop_name, prop_val))
|
||||
|
||||
file_types.append((file_type, proplist))
|
||||
# print file_types
|
||||
|
||||
|
||||
def runCommand(cmd):
|
||||
output_file = os.popen(cmd);
|
||||
output_text = output_file.read();
|
||||
exit_status = output_file.close();
|
||||
if exit_status:
|
||||
print >>sys.stderr, '"', cmd, '" failed. Exiting.'
|
||||
sys.exit(exit_status)
|
||||
return output_text
|
||||
|
||||
svn_auto_props = runCommand("svn propget svn:auto-props http://source.icu-project.org/repos/icu")
|
||||
|
||||
def usage():
|
||||
print "usage: " + sys.argv[0] + " [-f | --fix] [-h | --help]"
|
||||
|
||||
|
||||
#
|
||||
# UTF-8 file check. For text files with svn:mime-type=text/anything, check the specified charset
|
||||
# file_name: name of a text file.
|
||||
# base_mime_type: svn:mime-type property from the auto-props settings for this file type.
|
||||
# actual_mime_type: existing svn:mime-type property value for the file.
|
||||
# return: The correct svn:mime-type property value,
|
||||
# either the original, if it looks OK, otherwise the value from auto-props
|
||||
#
|
||||
def check_utf8(file_name, base_mime_type, actual_mime_type):
|
||||
|
||||
f = open(file_name, 'r')
|
||||
bytes = f.read()
|
||||
f.close()
|
||||
file_is_utf8 = True
|
||||
try:
|
||||
bytes.decode("UTF-8")
|
||||
except UnicodeDecodeError:
|
||||
file_is_utf8 = False
|
||||
|
||||
if not file_is_utf8 and actual_mime_type.find("utf-8") >= 0:
|
||||
print "Error: %s is not valid utf-8, but has a utf-8 mime type." % file_name
|
||||
return actual_mime_type
|
||||
|
||||
if file_is_utf8 and actual_mime_type.find("charset") >=0 and actual_mime_type.find("utf-8") < 0:
|
||||
print "Warning: %s is valid utf-8, but has a mime-type of %s." % (file_name, actual_mime_type)
|
||||
|
||||
if ord(bytes[0]) == 0xef:
|
||||
if not file_name.endswith(".txt"):
|
||||
print "Warning: file %s contains a UTF-8 BOM: " % file_name
|
||||
|
||||
# If the file already has a charset in its mime-type, don't make any change.
|
||||
|
||||
if actual_mime_type.find("charset=") >= 0:
|
||||
return actual_mime_type;
|
||||
|
||||
return base_mime_type
|
||||
|
||||
|
||||
def main(argv):
|
||||
fix_problems = False;
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "fh", ("fix", "help"))
|
||||
except getopt.GetoptError:
|
||||
print "unrecognized option: " + argv[0]
|
||||
usage()
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
usage()
|
||||
sys.exit()
|
||||
if opt in ("-f", "--fix"):
|
||||
fix_problems = True
|
||||
if args:
|
||||
print "unexpected command line argument"
|
||||
usage()
|
||||
sys.exit()
|
||||
|
||||
parse_auto_props()
|
||||
output = runCommand("svn ls -R ");
|
||||
file_list = output.splitlines()
|
||||
|
||||
for f in file_list:
|
||||
if os.path.isdir(f):
|
||||
# print "Skipping dir " + f
|
||||
continue
|
||||
if not os.path.isfile(f):
|
||||
print "Repository file not in working copy: " + f
|
||||
continue;
|
||||
|
||||
for file_pattern, props in file_types:
|
||||
if re.match(file_pattern, f):
|
||||
# print "doing " + f
|
||||
for propname, propval in props:
|
||||
actual_propval = runCommand("svn propget --strict " + propname + " " + f)
|
||||
#print propname + ": " + actual_propval
|
||||
if propname == "svn:mime-type" and propval.find("text/") == 0:
|
||||
# check for UTF-8 text files, should have svn:mime-type=text/something; charset=utf8
|
||||
propval = check_utf8(f, propval, actual_propval)
|
||||
if not (propval == actual_propval or (propval == "" and actual_propval == "*")):
|
||||
print "svn propset %s '%s' %s" % (propname, propval, f)
|
||||
if fix_problems:
|
||||
os.system("svn propset %s '%s' %s" % (propname, propval, f))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
@ -2,8 +2,11 @@ eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
|
||||
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
|
||||
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
@ -13,6 +16,7 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.APILeak=warning
|
||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
||||
@ -62,12 +66,14 @@ org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
|
||||
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
|
||||
org.eclipse.jdt.core.compiler.problem.nullReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
|
||||
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
|
||||
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
|
||||
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
|
||||
@ -84,12 +90,16 @@ org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
|
||||
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=info
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
||||
@ -97,6 +107,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
|
||||
|
@ -37,11 +37,11 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
|
||||
Object[] setComparatorParam;
|
||||
|
||||
public static <K, V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator) {
|
||||
return new Relation<K, V>(map, setCreator);
|
||||
return new Relation<>(map, setCreator);
|
||||
}
|
||||
|
||||
public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator, Comparator<V> setComparator) {
|
||||
return new Relation<K, V>(map, setCreator, setComparator);
|
||||
return new Relation<>(map, setCreator, setComparator);
|
||||
}
|
||||
|
||||
public Relation(Map<K, Set<V>> map, Class<?> setCreator) {
|
||||
@ -91,10 +91,10 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
|
||||
}
|
||||
|
||||
public Set<Entry<K, V>> keyValueSet() {
|
||||
Set<Entry<K, V>> result = new LinkedHashSet<Entry<K, V>>();
|
||||
Set<Entry<K, V>> result = new LinkedHashSet<>();
|
||||
for (K key : data.keySet()) {
|
||||
for (V value : data.get(key)) {
|
||||
result.add(new SimpleEntry<K, V>(key, value));
|
||||
result.add(new SimpleEntry<>(key, value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -320,7 +320,9 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
|
||||
return result;
|
||||
}
|
||||
|
||||
public Set<V> removeAll(K... keys) {
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs") // Not supported by Eclipse, but we need this for javac
|
||||
public final Set<V> removeAll(K... keys) {
|
||||
return removeAll(Arrays.asList(keys));
|
||||
}
|
||||
|
||||
@ -333,7 +335,7 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
|
||||
}
|
||||
|
||||
public Set<V> removeAll(Collection<K> toBeRemoved) {
|
||||
Set<V> result = new LinkedHashSet<V>();
|
||||
Set<V> result = new LinkedHashSet<>();
|
||||
for (K key : toBeRemoved) {
|
||||
try {
|
||||
final Set<V> removals = data.remove(key);
|
||||
|
@ -44,7 +44,9 @@ public class XCldrStub {
|
||||
? setClass
|
||||
: HashSet.class);
|
||||
}
|
||||
public Multimap<K, V> putAll(K key, V... values) {
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs") // Not supported by Eclipse, but we need this for javac
|
||||
public final Multimap<K, V> putAll(K key, V... values) {
|
||||
if (values.length != 0) {
|
||||
createSetIfMissing(key).addAll(Arrays.asList(values));
|
||||
}
|
||||
@ -107,7 +109,7 @@ public class XCldrStub {
|
||||
return map.size();
|
||||
}
|
||||
public Iterable<Entry<K, V>> entries() {
|
||||
return new MultimapIterator<K, V>(map);
|
||||
return new MultimapIterator<>(map);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
@ -147,7 +149,7 @@ public class XCldrStub {
|
||||
private static class MultimapIterator<K,V> implements Iterator<Entry<K,V>>, Iterable<Entry<K,V>> {
|
||||
private final Iterator<Entry<K, Set<V>>> it1;
|
||||
private Iterator<V> it2 = null;
|
||||
private final ReusableEntry<K,V> entry = new ReusableEntry<K,V>();
|
||||
private final ReusableEntry<K,V> entry = new ReusableEntry<>();
|
||||
|
||||
private MultimapIterator(Map<K,Set<V>> map) {
|
||||
it1 = map.entrySet().iterator();
|
||||
@ -199,7 +201,7 @@ public class XCldrStub {
|
||||
super(new HashMap<K, Set<V>>(), HashSet.class);
|
||||
}
|
||||
public static <K, V> HashMultimap<K, V> create() {
|
||||
return new HashMultimap<K, V>();
|
||||
return new HashMultimap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,7 +210,7 @@ public class XCldrStub {
|
||||
super(new TreeMap<K, Set<V>>(), TreeSet.class);
|
||||
}
|
||||
public static <K, V> TreeMultimap<K, V> create() {
|
||||
return new TreeMultimap<K, V>();
|
||||
return new TreeMultimap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +219,7 @@ public class XCldrStub {
|
||||
super(new LinkedHashMap<K, Set<V>>(), LinkedHashSet.class);
|
||||
}
|
||||
public static <K, V> LinkedHashMultimap<K, V> create() {
|
||||
return new LinkedHashMultimap<K, V>();
|
||||
return new LinkedHashMultimap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,24 +317,24 @@ public class XCldrStub {
|
||||
|
||||
public static class ImmutableSet {
|
||||
public static <T> Set<T> copyOf(Set<T> values) {
|
||||
return Collections.unmodifiableSet(new LinkedHashSet<T>(values)); // copy set for safety, preserve order
|
||||
return Collections.unmodifiableSet(new LinkedHashSet<>(values)); // copy set for safety, preserve order
|
||||
}
|
||||
}
|
||||
public static class ImmutableMap {
|
||||
public static <K,V> Map<K,V> copyOf(Map<K,V> values) {
|
||||
return Collections.unmodifiableMap(new LinkedHashMap<K,V>(values)); // copy set for safety, preserve order
|
||||
return Collections.unmodifiableMap(new LinkedHashMap<>(values)); // copy set for safety, preserve order
|
||||
}
|
||||
}
|
||||
public static class ImmutableMultimap {
|
||||
public static <K,V> Multimap<K,V> copyOf(Multimap<K,V> values) {
|
||||
LinkedHashMap<K, Set<V>> temp = new LinkedHashMap<K,Set<V>>(); // semi-deep copy, preserve order
|
||||
LinkedHashMap<K, Set<V>> temp = new LinkedHashMap<>(); // semi-deep copy, preserve order
|
||||
for (Entry<K, Set<V>> entry : values.asMap().entrySet()) {
|
||||
Set<V> value = entry.getValue();
|
||||
temp.put(entry.getKey(), value.size() == 1
|
||||
? Collections.singleton(value.iterator().next())
|
||||
: Collections.unmodifiableSet(new LinkedHashSet<V>(value)));
|
||||
: Collections.unmodifiableSet(new LinkedHashSet<>(value)));
|
||||
}
|
||||
return new Multimap<K,V>(Collections.unmodifiableMap(temp), null);
|
||||
return new Multimap<>(Collections.unmodifiableMap(temp), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,11 @@ package com.ibm.icu.impl.number;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
|
||||
/**
|
||||
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two
|
||||
* or more Modifiers and returns the modifier appropriate for the current situation.
|
||||
* This implementation of ModifierStore adopts references to Modifiers.
|
||||
*
|
||||
* (This is named "adopting" because in C++, this class takes ownership of the Modifiers.)
|
||||
*/
|
||||
public class ParameterizedModifier {
|
||||
public class AdoptingModifierStore implements ModifierStore {
|
||||
private final Modifier positive;
|
||||
private final Modifier zero;
|
||||
private final Modifier negative;
|
||||
@ -21,7 +22,7 @@ public class ParameterizedModifier {
|
||||
* <p>
|
||||
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
|
||||
*/
|
||||
public ParameterizedModifier(Modifier positive, Modifier zero, Modifier negative) {
|
||||
public AdoptingModifierStore(Modifier positive, Modifier zero, Modifier negative) {
|
||||
this.positive = positive;
|
||||
this.zero = zero;
|
||||
this.negative = negative;
|
||||
@ -36,7 +37,7 @@ public class ParameterizedModifier {
|
||||
* <p>
|
||||
* If this constructor is used, a plural form MUST be passed to {@link #getModifier}.
|
||||
*/
|
||||
public ParameterizedModifier() {
|
||||
public AdoptingModifierStore() {
|
||||
this.positive = null;
|
||||
this.zero = null;
|
||||
this.negative = null;
|
||||
@ -53,7 +54,7 @@ public class ParameterizedModifier {
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
public Modifier getModifier(int signum) {
|
||||
public Modifier getModifierWithoutPlural(int signum) {
|
||||
assert frozen;
|
||||
assert mods == null;
|
||||
return signum == 0 ? zero : signum < 0 ? negative : positive;
|
||||
@ -66,6 +67,8 @@ public class ParameterizedModifier {
|
||||
}
|
||||
|
||||
private static int getModIndex(int signum, StandardPlural plural) {
|
||||
assert signum >= -1 && signum <= 1;
|
||||
assert plural != null;
|
||||
return plural.ordinal() * 3 + (signum + 1);
|
||||
}
|
||||
}
|
@ -74,6 +74,28 @@ public class ConstantAffixModifier implements Modifier {
|
||||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
if (!(other instanceof ConstantAffixModifier)) {
|
||||
return false;
|
||||
}
|
||||
ConstantAffixModifier _other = (ConstantAffixModifier) other;
|
||||
return prefix.equals(_other.prefix) && suffix.equals(_other.suffix) && field == _other.field
|
||||
&& strong == _other.strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<ConstantAffixModifier prefix:'%s' suffix:'%s'>", prefix, suffix);
|
||||
|
@ -2,6 +2,8 @@
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
@ -20,17 +22,30 @@ public class ConstantMultiFieldModifier implements Modifier {
|
||||
private final boolean overwrite;
|
||||
private final boolean strong;
|
||||
|
||||
// Parameters: used for number range formatting
|
||||
private final Parameters parameters;
|
||||
|
||||
public ConstantMultiFieldModifier(
|
||||
NumberStringBuilder prefix,
|
||||
NumberStringBuilder suffix,
|
||||
boolean overwrite,
|
||||
boolean strong) {
|
||||
this(prefix, suffix, overwrite, strong, null);
|
||||
}
|
||||
|
||||
public ConstantMultiFieldModifier(
|
||||
NumberStringBuilder prefix,
|
||||
NumberStringBuilder suffix,
|
||||
boolean overwrite,
|
||||
boolean strong,
|
||||
Parameters parameters) {
|
||||
prefixChars = prefix.toCharArray();
|
||||
suffixChars = suffix.toCharArray();
|
||||
prefixFields = prefix.toFieldArray();
|
||||
suffixFields = suffix.toFieldArray();
|
||||
this.overwrite = overwrite;
|
||||
this.strong = strong;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,6 +74,40 @@ public class ConstantMultiFieldModifier implements Modifier {
|
||||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
for (int i = 0; i < prefixFields.length; i++) {
|
||||
if (prefixFields[i] == field) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < suffixFields.length; i++) {
|
||||
if (suffixFields[i] == field) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
if (!(other instanceof ConstantMultiFieldModifier)) {
|
||||
return false;
|
||||
}
|
||||
ConstantMultiFieldModifier _other = (ConstantMultiFieldModifier) other;
|
||||
if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
|
||||
return true;
|
||||
}
|
||||
return Arrays.equals(prefixChars, _other.prefixChars) && Arrays.equals(prefixFields, _other.prefixFields)
|
||||
&& Arrays.equals(suffixChars, _other.suffixChars) && Arrays.equals(suffixFields, _other.suffixFields)
|
||||
&& overwrite == _other.overwrite && strong == _other.strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
NumberStringBuilder temp = new NumberStringBuilder();
|
||||
|
@ -1014,6 +1014,46 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof DecimalQuantity_AbstractBCD)) {
|
||||
return false;
|
||||
}
|
||||
DecimalQuantity_AbstractBCD _other = (DecimalQuantity_AbstractBCD) other;
|
||||
|
||||
boolean basicEquals =
|
||||
scale == _other.scale
|
||||
&& precision == _other.precision
|
||||
&& flags == _other.flags
|
||||
&& lOptPos == _other.lOptPos
|
||||
&& lReqPos == _other.lReqPos
|
||||
&& rReqPos == _other.rReqPos
|
||||
&& rOptPos == _other.rOptPos
|
||||
&& isApproximate == _other.isApproximate;
|
||||
if (!basicEquals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (precision == 0) {
|
||||
return true;
|
||||
} else if (isApproximate) {
|
||||
return origDouble == _other.origDouble && origDelta == _other.origDelta;
|
||||
} else {
|
||||
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
|
||||
if (getDigit(m) != _other.getDigit(m)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
|
||||
*
|
||||
|
@ -21,7 +21,7 @@ import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class LongNameHandler implements MicroPropsGenerator {
|
||||
public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
||||
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + 1;
|
||||
@ -175,10 +175,11 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(locale, currency, simpleFormats);
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
|
||||
StandardPlural.class);
|
||||
simpleFormatsToModifiers(simpleFormats, null, modifiers);
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
|
||||
result.simpleFormatsToModifiers(simpleFormats, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LongNameHandler forMeasureUnit(
|
||||
@ -203,10 +204,11 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
getMeasureData(locale, unit, width, simpleFormats);
|
||||
// TODO: What field to use for units?
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
|
||||
StandardPlural.class);
|
||||
simpleFormatsToModifiers(simpleFormats, null, modifiers);
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
|
||||
result.simpleFormatsToModifiers(simpleFormats, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static LongNameHandler forCompoundUnit(
|
||||
@ -238,29 +240,32 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
|
||||
StandardPlural.class);
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null, modifiers);
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
|
||||
result.multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void simpleFormatsToModifiers(
|
||||
private void simpleFormatsToModifiers(
|
||||
String[] simpleFormats,
|
||||
NumberFormat.Field field,
|
||||
Map<StandardPlural, SimpleModifier> output) {
|
||||
NumberFormat.Field field) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
String simpleFormat = getWithPlural(simpleFormats, plural);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 0, 1);
|
||||
output.put(plural, new SimpleModifier(compiled, field, false));
|
||||
Modifier.Parameters parameters = new Modifier.Parameters();
|
||||
parameters.obj = this;
|
||||
parameters.signum = 0;
|
||||
parameters.plural = plural;
|
||||
modifiers.put(plural, new SimpleModifier(compiled, field, false, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
private static void multiSimpleFormatsToModifiers(
|
||||
private void multiSimpleFormatsToModifiers(
|
||||
String[] leadFormats,
|
||||
String trailFormat,
|
||||
NumberFormat.Field field,
|
||||
Map<StandardPlural, SimpleModifier> output) {
|
||||
NumberFormat.Field field) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
@ -268,7 +273,11 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
|
||||
String compoundCompiled = SimpleFormatterImpl
|
||||
.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
|
||||
output.put(plural, new SimpleModifier(compoundCompiled, field, false));
|
||||
Modifier.Parameters parameters = new Modifier.Parameters();
|
||||
parameters.obj = this;
|
||||
parameters.signum = 0;
|
||||
parameters.plural = plural;
|
||||
modifiers.put(plural, new SimpleModifier(compoundCompiled, field, false, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,4 +290,9 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
micros.modOuter = modifiers.get(copy.getStandardPlural(rules));
|
||||
return micros;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getModifier(int signum, StandardPlural plural) {
|
||||
return modifiers.get(plural);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
|
||||
* to the string builder. A Modifier usually contains a prefix and a suffix that are applied, but it
|
||||
@ -48,4 +51,30 @@ public interface Modifier {
|
||||
* @return Whether the modifier is strong.
|
||||
*/
|
||||
public boolean isStrong();
|
||||
|
||||
/**
|
||||
* Whether the modifier contains at least one occurrence of the given field.
|
||||
*/
|
||||
public boolean containsField(Field currency);
|
||||
|
||||
/**
|
||||
* A fill-in for getParameters(). obj will always be set; if non-null, the other
|
||||
* two fields are also safe to read.
|
||||
*/
|
||||
public static class Parameters {
|
||||
public ModifierStore obj;
|
||||
public int signum;
|
||||
public StandardPlural plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a set of "parameters" for this Modifier.
|
||||
*/
|
||||
public Parameters getParameters();
|
||||
|
||||
/**
|
||||
* Returns whether this Modifier is *semantically equivalent* to the other Modifier;
|
||||
* in many cases, this is the same as equal, but parameters should be ignored.
|
||||
*/
|
||||
public boolean semanticallyEquivalent(Modifier other);
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
|
||||
/**
|
||||
* This is *not* a modifier; rather, it is an object that can return modifiers
|
||||
* based on given parameters.
|
||||
*
|
||||
* @author sffc
|
||||
*/
|
||||
public interface ModifierStore {
|
||||
/**
|
||||
* Returns a Modifier with the given parameters (best-effort).
|
||||
*/
|
||||
Modifier getModifier(int signum, StandardPlural plural);
|
||||
}
|
@ -7,6 +7,7 @@ import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
|
||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.Currency;
|
||||
|
||||
@ -165,7 +166,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
||||
NumberStringBuilder b = new NumberStringBuilder();
|
||||
if (needsPlurals()) {
|
||||
// Slower path when we require the plural keyword.
|
||||
ParameterizedModifier pm = new ParameterizedModifier();
|
||||
AdoptingModifierStore pm = new AdoptingModifierStore();
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
setNumberProperties(1, plural);
|
||||
pm.setModifier(1, plural, createConstantModifier(a, b));
|
||||
@ -184,7 +185,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
||||
Modifier zero = createConstantModifier(a, b);
|
||||
setNumberProperties(-1, null);
|
||||
Modifier negative = createConstantModifier(a, b);
|
||||
ParameterizedModifier pm = new ParameterizedModifier(positive, zero, negative);
|
||||
AdoptingModifierStore pm = new AdoptingModifierStore(positive, zero, negative);
|
||||
return new ImmutablePatternModifier(pm, null, parent);
|
||||
}
|
||||
}
|
||||
@ -213,12 +214,12 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
||||
}
|
||||
|
||||
public static class ImmutablePatternModifier implements MicroPropsGenerator {
|
||||
final ParameterizedModifier pm;
|
||||
final AdoptingModifierStore pm;
|
||||
final PluralRules rules;
|
||||
final MicroPropsGenerator parent;
|
||||
|
||||
ImmutablePatternModifier(
|
||||
ParameterizedModifier pm,
|
||||
AdoptingModifierStore pm,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
this.pm = pm;
|
||||
@ -235,7 +236,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
||||
|
||||
public void applyToMicros(MicroProps micros, DecimalQuantity quantity) {
|
||||
if (rules == null) {
|
||||
micros.modMiddle = pm.getModifier(quantity.signum());
|
||||
micros.modMiddle = pm.getModifierWithoutPlural(quantity.signum());
|
||||
} else {
|
||||
// TODO: Fix this. Avoid the copy.
|
||||
DecimalQuantity copy = quantity.createCopy();
|
||||
@ -319,6 +320,27 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
||||
return isStrong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int insertPrefix(NumberStringBuilder sb, int position) {
|
||||
prepareAffix(true);
|
||||
int length = AffixUtils.unescape(currentAffix, sb, position, this);
|
||||
|
@ -3,7 +3,9 @@
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
import com.ibm.icu.util.ICUException;
|
||||
|
||||
/**
|
||||
* The second primary implementation of {@link Modifier}, this one consuming a
|
||||
@ -17,15 +19,24 @@ public class SimpleModifier implements Modifier {
|
||||
private final int suffixOffset;
|
||||
private final int suffixLength;
|
||||
|
||||
// Parameters: used for number range formatting
|
||||
private final Parameters parameters;
|
||||
|
||||
/** TODO: This is copied from SimpleFormatterImpl. */
|
||||
private static final int ARG_NUM_LIMIT = 0x100;
|
||||
|
||||
/** Creates a modifier that uses the SimpleFormatter string formats. */
|
||||
public SimpleModifier(String compiledPattern, Field field, boolean strong) {
|
||||
this(compiledPattern, field, strong, null);
|
||||
}
|
||||
|
||||
/** Creates a modifier that uses the SimpleFormatter string formats. */
|
||||
public SimpleModifier(String compiledPattern, Field field, boolean strong, Parameters parameters) {
|
||||
assert compiledPattern != null;
|
||||
this.compiledPattern = compiledPattern;
|
||||
this.field = field;
|
||||
this.strong = strong;
|
||||
this.parameters = parameters;
|
||||
|
||||
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
|
||||
if (argLimit == 0) {
|
||||
@ -80,6 +91,30 @@ public class SimpleModifier implements Modifier {
|
||||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
if (!(other instanceof SimpleModifier)) {
|
||||
return false;
|
||||
}
|
||||
SimpleModifier _other = (SimpleModifier) other;
|
||||
if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
|
||||
return true;
|
||||
}
|
||||
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
|
||||
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
|
||||
@ -123,4 +158,66 @@ public class SimpleModifier implements Modifier {
|
||||
return prefixLength + suffixLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
|
||||
* I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other.
|
||||
*
|
||||
* <p>
|
||||
* Applies the compiled two-argument pattern to the NumberStringBuilder.
|
||||
*
|
||||
* <p>
|
||||
* This method is optimized for the case where the prefix and suffix are often empty, such as
|
||||
* in the range pattern like "{0}-{1}".
|
||||
*/
|
||||
public static void formatTwoArgPattern(String compiledPattern, NumberStringBuilder result, int index, PrefixInfixSuffixLengthHelper h,
|
||||
Field field) {
|
||||
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
|
||||
if (argLimit != 2) {
|
||||
throw new ICUException();
|
||||
}
|
||||
int offset = 1; // offset into compiledPattern
|
||||
int length = 0; // chars added to result
|
||||
|
||||
int prefixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (prefixLength < ARG_NUM_LIMIT) {
|
||||
// No prefix
|
||||
prefixLength = 0;
|
||||
} else {
|
||||
prefixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + prefixLength, field);
|
||||
offset += prefixLength;
|
||||
length += prefixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int infixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (infixLength < ARG_NUM_LIMIT) {
|
||||
// No infix
|
||||
infixLength = 0;
|
||||
} else {
|
||||
infixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + infixLength, field);
|
||||
offset += infixLength;
|
||||
length += infixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int suffixLength;
|
||||
if (offset == compiledPattern.length()) {
|
||||
// No suffix
|
||||
suffixLength = 0;
|
||||
} else {
|
||||
suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
|
||||
offset++;
|
||||
result.insert(index + length, compiledPattern, offset, offset + suffixLength, field);
|
||||
length += suffixLength;
|
||||
}
|
||||
|
||||
h.lengthPrefix = prefixLength;
|
||||
h.lengthInfix = infixLength;
|
||||
h.lengthSuffix = suffixLength;
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,11 @@ public class ScientificMatcher implements NumberParseMatcher {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only accept one exponent per string.
|
||||
if (0 != (result.flags & ParsedNumber.FLAG_HAS_EXPONENT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First match the scientific separator, and then match another number after it.
|
||||
// NOTE: This is guarded by the smoke test; no need to check exponentSeparatorString length again.
|
||||
int overlap1 = segment.getCommonPrefixLength(exponentSeparatorString);
|
||||
|
@ -0,0 +1,30 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number.range;
|
||||
|
||||
/**
|
||||
* A small, mutable internal helper class for keeping track of offsets on range patterns.
|
||||
*/
|
||||
public class PrefixInfixSuffixLengthHelper {
|
||||
public int lengthPrefix = 0;
|
||||
public int length1 = 0;
|
||||
public int lengthInfix = 0;
|
||||
public int length2 = 0;
|
||||
public int lengthSuffix = 0;
|
||||
|
||||
public int index0() {
|
||||
return lengthPrefix;
|
||||
}
|
||||
|
||||
public int index1() {
|
||||
return lengthPrefix + length1;
|
||||
}
|
||||
|
||||
public int index2() {
|
||||
return lengthPrefix + length1 + lengthInfix;
|
||||
}
|
||||
|
||||
public int index3() {
|
||||
return lengthPrefix + length1 + lengthInfix + length2;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number.range;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.number.UnlocalizedNumberFormatter;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* @author sffc
|
||||
*
|
||||
*/
|
||||
public class RangeMacroProps {
|
||||
public UnlocalizedNumberFormatter formatter1;
|
||||
public UnlocalizedNumberFormatter formatter2;
|
||||
public int sameFormatters = -1; // -1 for unset, 0 for false, 1 for true
|
||||
public RangeCollapse collapse;
|
||||
public RangeIdentityFallback identityFallback;
|
||||
public ULocale loc;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(formatter1,
|
||||
formatter2,
|
||||
collapse,
|
||||
identityFallback,
|
||||
loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object _other) {
|
||||
if (_other == null)
|
||||
return false;
|
||||
if (this == _other)
|
||||
return true;
|
||||
if (!(_other instanceof RangeMacroProps))
|
||||
return false;
|
||||
RangeMacroProps other = (RangeMacroProps) _other;
|
||||
return Objects.equals(formatter1, other.formatter1)
|
||||
&& Objects.equals(formatter2, other.formatter2)
|
||||
&& Objects.equals(collapse, other.collapse)
|
||||
&& Objects.equals(identityFallback, other.identityFallback)
|
||||
&& Objects.equals(loc, other.loc);
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number.range;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
/**
|
||||
* @author sffc
|
||||
*
|
||||
*/
|
||||
public class StandardPluralRanges {
|
||||
|
||||
StandardPlural[] flatTriples;
|
||||
int numTriples = 0;
|
||||
|
||||
////////////////////
|
||||
|
||||
private static final class PluralRangesDataSink extends UResource.Sink {
|
||||
|
||||
StandardPluralRanges output;
|
||||
|
||||
PluralRangesDataSink(StandardPluralRanges output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Array entriesArray = value.getArray();
|
||||
output.setCapacity(entriesArray.getSize());
|
||||
for (int i = 0; entriesArray.getValue(i, value); ++i) {
|
||||
UResource.Array pluralFormsArray = value.getArray();
|
||||
pluralFormsArray.getValue(0, value);
|
||||
StandardPlural first = StandardPlural.fromString(value.getString());
|
||||
pluralFormsArray.getValue(1, value);
|
||||
StandardPlural second = StandardPlural.fromString(value.getString());
|
||||
pluralFormsArray.getValue(2, value);
|
||||
StandardPlural result = StandardPlural.fromString(value.getString());
|
||||
output.addPluralRange(first, second, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getPluralRangesData(
|
||||
ULocale locale,
|
||||
StandardPluralRanges out) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "pluralRanges");
|
||||
sb.append("locales/");
|
||||
sb.append(locale.getLanguage());
|
||||
String key = sb.toString();
|
||||
String set;
|
||||
try {
|
||||
set = resource.getStringWithFallback(key);
|
||||
} catch (MissingResourceException e) {
|
||||
// Not all languages are covered: fail gracefully
|
||||
return;
|
||||
}
|
||||
|
||||
sb.setLength(0);
|
||||
sb.append("rules/");
|
||||
sb.append(set);
|
||||
key = sb.toString();
|
||||
PluralRangesDataSink sink = new PluralRangesDataSink(out);
|
||||
resource.getAllItemsWithFallback(key, sink);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
||||
public StandardPluralRanges(ULocale locale) {
|
||||
getPluralRangesData(locale, this);
|
||||
}
|
||||
|
||||
/** Used for data loading. */
|
||||
private void addPluralRange(StandardPlural first, StandardPlural second, StandardPlural result) {
|
||||
flatTriples[3 * numTriples] = first;
|
||||
flatTriples[3 * numTriples + 1] = second;
|
||||
flatTriples[3 * numTriples + 2] = result;
|
||||
numTriples++;
|
||||
}
|
||||
|
||||
/** Used for data loading. */
|
||||
private void setCapacity(int length) {
|
||||
flatTriples = new StandardPlural[length*3];
|
||||
}
|
||||
|
||||
public StandardPlural resolve(StandardPlural first, StandardPlural second) {
|
||||
for (int i = 0; i < numTriples; i++) {
|
||||
if (first == flatTriples[3 * i] && second == flatTriples[3 * i + 1]) {
|
||||
return flatTriples[3 * i + 2];
|
||||
}
|
||||
}
|
||||
// Default fallback
|
||||
return StandardPlural.OTHER;
|
||||
}
|
||||
|
||||
}
|
@ -70,9 +70,9 @@ public class FormattedNumber {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the start and end indices of the first occurrence of the given <em>field</em> in the
|
||||
* output string. This allows you to determine the locations of the integer part, fraction part, and
|
||||
* sign.
|
||||
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
|
||||
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
|
||||
* the integer part, fraction part, or symbols.
|
||||
*
|
||||
* <p>
|
||||
* If multiple different field attributes are needed, this method can be called repeatedly, or if
|
||||
|
@ -0,0 +1,209 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
|
||||
import com.ibm.icu.util.ICUUncheckedIOException;
|
||||
|
||||
/**
|
||||
* The result of a number range formatting operation. This class allows the result to be exported in several data types,
|
||||
* including a String, an AttributedCharacterIterator, and a BigDecimal.
|
||||
*
|
||||
* @author sffc
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public class FormattedNumberRange {
|
||||
final NumberStringBuilder string;
|
||||
final DecimalQuantity quantity1;
|
||||
final DecimalQuantity quantity2;
|
||||
final RangeIdentityResult identityResult;
|
||||
|
||||
FormattedNumberRange(NumberStringBuilder string, DecimalQuantity quantity1, DecimalQuantity quantity2,
|
||||
RangeIdentityResult identityResult) {
|
||||
this.string = string;
|
||||
this.quantity1 = quantity1;
|
||||
this.quantity2 = quantity2;
|
||||
this.identityResult = identityResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a String representation of the the formatted number range.
|
||||
*
|
||||
* @return a String containing the localized number range.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the formatted number range to an Appendable, such as a StringBuilder. This may be slightly more efficient
|
||||
* than creating a String.
|
||||
*
|
||||
* <p>
|
||||
* If an IOException occurs when appending to the Appendable, an unchecked {@link ICUUncheckedIOException} is thrown
|
||||
* instead.
|
||||
*
|
||||
* @param appendable
|
||||
* The Appendable to which to append the formatted number range string.
|
||||
* @return The same Appendable, for chaining.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see Appendable
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public <A extends Appendable> A appendTo(A appendable) {
|
||||
try {
|
||||
appendable.append(string);
|
||||
} catch (IOException e) {
|
||||
// Throw as an unchecked exception to avoid users needing try/catch
|
||||
throw new ICUUncheckedIOException(e);
|
||||
}
|
||||
return appendable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
|
||||
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
|
||||
* the integer part, fraction part, or symbols.
|
||||
* <p>
|
||||
* If both sides of the range have the same field, the field will occur twice, once before the range separator and
|
||||
* once after the range separator, if applicable.
|
||||
* <p>
|
||||
* If a field occurs just once, calling this method will find that occurrence and return it. If a field occurs
|
||||
* multiple times, this method may be called repeatedly with the following pattern:
|
||||
*
|
||||
* <pre>
|
||||
* FieldPosition fpos = new FieldPosition(NumberFormat.Field.INTEGER);
|
||||
* while (formattedNumberRange.nextFieldPosition(fpos, status)) {
|
||||
* // do something with fpos.
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* This method is useful if you know which field to query. If you want all available field position information, use
|
||||
* {@link #toCharacterIterator()}.
|
||||
*
|
||||
* @param fieldPosition
|
||||
* Input+output variable. See {@link FormattedNumber#nextFieldPosition(FieldPosition)}.
|
||||
* @return true if a new occurrence of the field was found; false otherwise.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see com.ibm.icu.text.NumberFormat.Field
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public boolean nextFieldPosition(FieldPosition fieldPosition) {
|
||||
return string.nextFieldPosition(fieldPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the formatted number range as an AttributedCharacterIterator. This allows you to determine which
|
||||
* characters in the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and
|
||||
* sign.
|
||||
* <p>
|
||||
* If information on only one field is needed, use {@link #nextFieldPosition(FieldPosition)} instead.
|
||||
*
|
||||
* @return An AttributedCharacterIterator, containing information on the field attributes of the number range
|
||||
* string.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see com.ibm.icu.text.NumberFormat.Field
|
||||
* @see AttributedCharacterIterator
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public AttributedCharacterIterator toCharacterIterator() {
|
||||
return string.toCharacterIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the first formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
|
||||
* printed after scaling and rounding have been applied by the number range formatting pipeline.
|
||||
*
|
||||
* @return A BigDecimal representation of the first formatted number.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
* @see #getSecondBigDecimal
|
||||
*/
|
||||
public BigDecimal getFirstBigDecimal() {
|
||||
return quantity1.toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the second formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
|
||||
* printed after scaling and rounding have been applied by the number range formatting pipeline.
|
||||
*
|
||||
* @return A BigDecimal representation of the second formatted number.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
* @see #getFirstBigDecimal
|
||||
*/
|
||||
public BigDecimal getSecondBigDecimal() {
|
||||
return quantity2.toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the pair of numbers was successfully formatted as a range or whether an identity fallback was
|
||||
* used. For example, if the first and second number were the same either before or after rounding occurred, an
|
||||
* identity fallback was used.
|
||||
*
|
||||
* @return A RangeIdentityType indicating the resulting identity situation in the formatted number range.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
* @see NumberRangeFormatter.RangeIdentityFallback
|
||||
*/
|
||||
public RangeIdentityResult getIdentityResult() {
|
||||
return identityResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
return Arrays.hashCode(string.toCharArray()) ^ Arrays.hashCode(string.toFieldArray())
|
||||
^ quantity1.toBigDecimal().hashCode() ^ quantity2.toBigDecimal().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other)
|
||||
return true;
|
||||
if (other == null)
|
||||
return false;
|
||||
if (!(other instanceof FormattedNumberRange))
|
||||
return false;
|
||||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
FormattedNumberRange _other = (FormattedNumberRange) other;
|
||||
return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
|
||||
&& Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
|
||||
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
|
||||
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
|
||||
}
|
||||
}
|
@ -152,9 +152,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
public FormattedNumber format(DecimalQuantity fq) {
|
||||
NumberStringBuilder string = new NumberStringBuilder();
|
||||
if (computeCompiled()) {
|
||||
compiled.apply(fq, string);
|
||||
compiled.format(fq, string);
|
||||
} else {
|
||||
NumberFormatterImpl.applyStatic(resolve(), fq, string);
|
||||
NumberFormatterImpl.formatStatic(resolve(), fq, string);
|
||||
}
|
||||
return new FormattedNumber(string, fq);
|
||||
}
|
||||
@ -190,7 +190,7 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
||||
// Further benchmarking is required.
|
||||
long currentCount = callCount.incrementAndGet(this);
|
||||
if (currentCount == macros.threshold.longValue()) {
|
||||
compiled = NumberFormatterImpl.fromMacros(macros);
|
||||
compiled = new NumberFormatterImpl(macros);
|
||||
return true;
|
||||
} else if (compiled != null) {
|
||||
return true;
|
||||
|
@ -0,0 +1,98 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||||
|
||||
/**
|
||||
* A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
|
||||
*
|
||||
* @author sffc
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<LocalizedNumberRangeFormatter> {
|
||||
|
||||
private volatile NumberRangeFormatterImpl fImpl;
|
||||
|
||||
LocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
|
||||
super(parent, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given integers to a string using the settings specified in the NumberRangeFormatter fluent setting
|
||||
* chain.
|
||||
*
|
||||
* @param first
|
||||
* The first number in the range, usually to the left in LTR locales.
|
||||
* @param second
|
||||
* The second number in the range, usually to the right in LTR locales.
|
||||
* @return A FormattedNumberRange object; call .toString() to get the string.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public FormattedNumberRange formatRange(int first, int second) {
|
||||
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
|
||||
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
|
||||
return formatImpl(dq1, dq2, first == second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given doubles to a string using the settings specified in the NumberRangeFormatter fluent setting
|
||||
* chain.
|
||||
*
|
||||
* @param first
|
||||
* The first number in the range, usually to the left in LTR locales.
|
||||
* @param second
|
||||
* The second number in the range, usually to the right in LTR locales.
|
||||
* @return A FormattedNumberRange object; call .toString() to get the string.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public FormattedNumberRange formatRange(double first, double second) {
|
||||
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
|
||||
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
|
||||
// Note: double equality could be changed to epsilon equality later if there is demand.
|
||||
// The epsilon should be set via an API method.
|
||||
return formatImpl(dq1, dq2, first == second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given Numbers to a string using the settings specified in the NumberRangeFormatter fluent setting
|
||||
* chain.
|
||||
*
|
||||
* @param first
|
||||
* The first number in the range, usually to the left in LTR locales.
|
||||
* @param second
|
||||
* The second number in the range, usually to the right in LTR locales.
|
||||
* @return A FormattedNumberRange object; call .toString() to get the string.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public FormattedNumberRange formatRange(Number first, Number second) {
|
||||
if (first == null || second == null) {
|
||||
throw new IllegalArgumentException("Cannot format null values in range");
|
||||
}
|
||||
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
|
||||
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
|
||||
return formatImpl(dq1, dq2, first.equals(second));
|
||||
}
|
||||
|
||||
FormattedNumberRange formatImpl(DecimalQuantity first, DecimalQuantity second, boolean equalBeforeRounding) {
|
||||
if (fImpl == null) {
|
||||
fImpl = new NumberRangeFormatterImpl(resolve());
|
||||
}
|
||||
return fImpl.format(first, second, equalBeforeRounding);
|
||||
}
|
||||
|
||||
@Override
|
||||
LocalizedNumberRangeFormatter create(int key, Object value) {
|
||||
return new LocalizedNumberRangeFormatter(this, key, value);
|
||||
}
|
||||
|
||||
}
|
@ -42,21 +42,21 @@ import com.ibm.icu.util.MeasureUnit;
|
||||
class NumberFormatterImpl {
|
||||
|
||||
/** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */
|
||||
public static NumberFormatterImpl fromMacros(MacroProps macros) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, true);
|
||||
return new NumberFormatterImpl(microPropsGenerator);
|
||||
public NumberFormatterImpl(MacroProps macros) {
|
||||
this(macrosToMicroGenerator(macros, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
public static void applyStatic(
|
||||
public static int formatStatic(
|
||||
MacroProps macros,
|
||||
DecimalQuantity inValue,
|
||||
NumberStringBuilder outString) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
microsToString(micros, inValue, outString);
|
||||
MicroProps micros = preProcessUnsafe(macros, inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
length += writeAffixes(micros, outString, 0, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,9 +82,40 @@ class NumberFormatterImpl {
|
||||
this.microPropsGenerator = microPropsGenerator;
|
||||
}
|
||||
|
||||
public void apply(DecimalQuantity inValue, NumberStringBuilder outString) {
|
||||
/**
|
||||
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||||
*/
|
||||
public int format(DecimalQuantity inValue, NumberStringBuilder outString) {
|
||||
MicroProps micros = preProcess(inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
length += writeAffixes(micros, outString, 0, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like format(), but saves the result into an output MicroProps without additional processing.
|
||||
*/
|
||||
public MicroProps preProcess(DecimalQuantity inValue) {
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
microsToString(micros, inValue, outString);
|
||||
micros.rounder.apply(inValue);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
|
||||
private static MicroProps preProcessUnsafe(MacroProps macros, DecimalQuantity inValue) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
micros.rounder.apply(inValue);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
|
||||
public int getPrefixSuffix(byte signum, StandardPlural plural, NumberStringBuilder output) {
|
||||
@ -350,64 +381,55 @@ class NumberFormatterImpl {
|
||||
//////////
|
||||
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
*
|
||||
* @param micros
|
||||
* The MicroProps after the quantity has been consumed. Will not be mutated.
|
||||
* @param quantity
|
||||
* The DecimalQuantity to be rendered. May be mutated.
|
||||
* @param string
|
||||
* The output string. Will be mutated.
|
||||
* Adds the affixes. Intended to be called immediately after formatNumber.
|
||||
*/
|
||||
private static void microsToString(
|
||||
public static int writeAffixes(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
micros.rounder.apply(quantity);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
quantity.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
int length = writeNumber(micros, quantity, string);
|
||||
// NOTE: When range formatting is added, these modifiers can bubble up.
|
||||
// For now, apply them all here at once.
|
||||
NumberStringBuilder string,
|
||||
int start,
|
||||
int end) {
|
||||
// Always apply the inner modifier (which is "strong").
|
||||
length += micros.modInner.apply(string, 0, length);
|
||||
int length = micros.modInner.apply(string, start, end);
|
||||
if (micros.padding.isValid()) {
|
||||
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, 0, length);
|
||||
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, start, end + length);
|
||||
} else {
|
||||
length += micros.modMiddle.apply(string, 0, length);
|
||||
length += micros.modOuter.apply(string, 0, length);
|
||||
length += micros.modMiddle.apply(string, start, end + length);
|
||||
length += micros.modOuter.apply(string, start, end + length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int writeNumber(
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
* This method formats only the main number, not affixes.
|
||||
*/
|
||||
public static int writeNumber(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
if (quantity.isInfinite()) {
|
||||
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
length += string.insert(length + index, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else if (quantity.isNaN()) {
|
||||
length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||||
length += string.insert(length + index, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else {
|
||||
// Add the integer digits
|
||||
length += writeIntegerDigits(micros, quantity, string);
|
||||
length += writeIntegerDigits(micros, quantity, string, length + index);
|
||||
|
||||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0
|
||||
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||||
length += string.insert(length,
|
||||
length += string.insert(length + index,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
length += writeFractionDigits(micros, quantity, string);
|
||||
length += writeFractionDigits(micros, quantity, string, length + index);
|
||||
}
|
||||
|
||||
return length;
|
||||
@ -416,13 +438,14 @@ class NumberFormatterImpl {
|
||||
private static int writeIntegerDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
|
||||
for (int i = 0; i < integerCount; i++) {
|
||||
// Add grouping separator
|
||||
if (micros.grouping.groupAtPosition(i, quantity)) {
|
||||
length += string.insert(0,
|
||||
length += string.insert(index,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
|
||||
: micros.symbols.getGroupingSeparatorString(),
|
||||
NumberFormat.Field.GROUPING_SEPARATOR);
|
||||
@ -431,11 +454,11 @@ class NumberFormatterImpl {
|
||||
// Get and append the next digit value
|
||||
byte nextDigit = quantity.getDigit(i);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length += string.insertCodePoint(0,
|
||||
length += string.insertCodePoint(index,
|
||||
micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.INTEGER);
|
||||
} else {
|
||||
length += string.insert(0,
|
||||
length += string.insert(index,
|
||||
micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.INTEGER);
|
||||
}
|
||||
@ -446,17 +469,18 @@ class NumberFormatterImpl {
|
||||
private static int writeFractionDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
int fractionCount = -quantity.getLowerDisplayMagnitude();
|
||||
for (int i = 0; i < fractionCount; i++) {
|
||||
// Get and append the next digit value
|
||||
byte nextDigit = quantity.getDigit(-i - 1);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
|
||||
length += string.insertCodePoint(length + index, micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.FRACTION);
|
||||
} else {
|
||||
length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
length += string.insert(length + index, micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.FRACTION);
|
||||
}
|
||||
}
|
||||
|
@ -47,10 +47,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
static final int KEY_PER_UNIT = 15;
|
||||
static final int KEY_MAX = 16;
|
||||
|
||||
final NumberFormatterSettings<?> parent;
|
||||
final int key;
|
||||
final Object value;
|
||||
volatile MacroProps resolvedMacros;
|
||||
private final NumberFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
private final Object value;
|
||||
private volatile MacroProps resolvedMacros;
|
||||
|
||||
NumberFormatterSettings(NumberFormatterSettings<?> parent, int key, Object value) {
|
||||
this.parent = parent;
|
||||
|
@ -263,8 +263,8 @@ final class NumberPropertyMapper {
|
||||
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
|
||||
if (macros.precision instanceof FractionPrecision) {
|
||||
// For the purposes of rounding, get the original min/max int/frac, since the local
|
||||
// variables
|
||||
// have been manipulated for display purposes.
|
||||
// variables have been manipulated for display purposes.
|
||||
int maxInt_ = properties.getMaximumIntegerDigits();
|
||||
int minInt_ = properties.getMinimumIntegerDigits();
|
||||
int minFrac_ = properties.getMinimumFractionDigits();
|
||||
int maxFrac_ = properties.getMaximumFractionDigits();
|
||||
@ -275,8 +275,15 @@ final class NumberPropertyMapper {
|
||||
// Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
|
||||
macros.precision = Precision.constructSignificant(1, maxFrac_ + 1).withMode(mathContext);
|
||||
} else {
|
||||
// All other scientific patterns, which mean round to minInt+maxFrac
|
||||
macros.precision = Precision.constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_)
|
||||
int maxSig_ = minInt_ + maxFrac_;
|
||||
// Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
|
||||
if (maxInt_ > minInt_ && minInt_ > 1) {
|
||||
minInt_ = 1;
|
||||
}
|
||||
int minSig_ = minInt_ + minFrac_;
|
||||
// To avoid regression, maxSig is not reset when minInt_ set to 1.
|
||||
// TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
|
||||
macros.precision = Precision.constructSignificant(minSig_, maxSig_)
|
||||
.withMode(mathContext);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,209 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
|
||||
* <p>
|
||||
* Usage example:
|
||||
* <pre>
|
||||
* NumberRangeFormatter.with()
|
||||
* .identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE)
|
||||
* .numberFormatterFirst(NumberFormatter.with().unit(MeasureUnit.METER))
|
||||
* .numberFormatterSecond(NumberFormatter.with().unit(MeasureUnit.KILOMETER))
|
||||
* .locale(ULocale.UK)
|
||||
* .formatRange(750, 1.2)
|
||||
* .toString();
|
||||
* // => "750 m - 1.2 km"
|
||||
* </pre>
|
||||
* <p>
|
||||
* Like NumberFormatter, NumberRangeFormatter instances are immutable and thread-safe. This API is based on the
|
||||
* <em>fluent</em> design pattern popularized by libraries such as Google's Guava.
|
||||
*
|
||||
* @author sffc
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
*/
|
||||
public abstract class NumberRangeFormatter {
|
||||
|
||||
/**
|
||||
* Defines how to merge fields that are identical across the range sign.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public enum RangeCollapse {
|
||||
/**
|
||||
* Use locale data and heuristics to determine how much of the string to collapse. Could end up collapsing none,
|
||||
* some, or all repeated pieces in a locale-sensitive way.
|
||||
* <p>
|
||||
* The heuristics used for this option are subject to change over time.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
AUTO,
|
||||
|
||||
/**
|
||||
* Do not collapse any part of the number. Example: "3.2 thousand kilograms – 5.3 thousand kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Collapse the unit part of the number, but not the notation, if present. Example: "3.2 thousand – 5.3 thousand
|
||||
* kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
UNIT,
|
||||
|
||||
/**
|
||||
* Collapse any field that is equal across the range sign. May introduce ambiguity on the magnitude of the
|
||||
* number. Example: "3.2 – 5.3 thousand kilograms"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the behavior when the two numbers in the range are identical after rounding. To programmatically detect
|
||||
* when the identity fallback is used, compare the lower and upper BigDecimals via FormattedNumber.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public static enum RangeIdentityFallback {
|
||||
/**
|
||||
* Show the number as a single value rather than a range. Example: "$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
SINGLE_VALUE,
|
||||
|
||||
/**
|
||||
* Show the number using a locale-sensitive approximation pattern. If the numbers were the same before rounding,
|
||||
* show the single value. Example: "~$5" or "$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
APPROXIMATELY_OR_SINGLE_VALUE,
|
||||
|
||||
/**
|
||||
* Show the number using a locale-sensitive approximation pattern. Use the range pattern always, even if the
|
||||
* inputs are the same. Example: "~$5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
APPROXIMATELY,
|
||||
|
||||
/**
|
||||
* Show the number as the range of two equal values. Use the range pattern always, even if the inputs are the
|
||||
* same. Example (with RangeCollapse.NONE): "$5 – $5"
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
RANGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the result class FormattedNumberRange to indicate to the user whether the numbers formatted in the range
|
||||
* were equal or not, and whether or not the identity fallback was applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public static enum RangeIdentityResult {
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were equal, even before any rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
EQUAL_BEFORE_ROUNDING,
|
||||
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were equal, but only after rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
EQUAL_AFTER_ROUNDING,
|
||||
|
||||
/**
|
||||
* Used to indicate that the two numbers in the range were not equal, even after rounding rules were applied.
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
NOT_EQUAL
|
||||
}
|
||||
|
||||
private static final UnlocalizedNumberRangeFormatter BASE = new UnlocalizedNumberRangeFormatter();
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is not currently
|
||||
* known at the call site.
|
||||
*
|
||||
* @return An {@link UnlocalizedNumberRangeFormatter}, to be used for chaining.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
public static UnlocalizedNumberRangeFormatter with() {
|
||||
return BASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
|
||||
* site.
|
||||
*
|
||||
* @param locale
|
||||
* The locale from which to load formats and symbols for number range formatting.
|
||||
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
public static LocalizedNumberRangeFormatter withLocale(Locale locale) {
|
||||
return BASE.locale(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
|
||||
* site.
|
||||
*
|
||||
* @param locale
|
||||
* The locale from which to load formats and symbols for number range formatting.
|
||||
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
|
||||
* @draft ICU 63
|
||||
*/
|
||||
public static LocalizedNumberRangeFormatter withLocale(ULocale locale) {
|
||||
return BASE.locale(locale);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.PatternProps;
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.MicroProps;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.SimpleModifier;
|
||||
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
|
||||
import com.ibm.icu.impl.number.range.RangeMacroProps;
|
||||
import com.ibm.icu.impl.number.range.StandardPluralRanges;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
/**
|
||||
* Business logic behind NumberRangeFormatter.
|
||||
*/
|
||||
class NumberRangeFormatterImpl {
|
||||
|
||||
final NumberFormatterImpl formatterImpl1;
|
||||
final NumberFormatterImpl formatterImpl2;
|
||||
final boolean fSameFormatters;
|
||||
|
||||
final NumberRangeFormatter.RangeCollapse fCollapse;
|
||||
final NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
|
||||
|
||||
// Should be final, but they are set in a helper function, not the constructor proper.
|
||||
// TODO: Clean up to make these fields actually final.
|
||||
/* final */ String fRangePattern;
|
||||
/* final */ SimpleModifier fApproximatelyModifier;
|
||||
|
||||
final StandardPluralRanges fPluralRanges;
|
||||
|
||||
////////////////////
|
||||
|
||||
// Helper function for 2-dimensional switch statement
|
||||
int identity2d(RangeIdentityFallback a, RangeIdentityResult b) {
|
||||
return a.ordinal() | (b.ordinal() << 4);
|
||||
}
|
||||
|
||||
private static final class NumberRangeDataSink extends UResource.Sink {
|
||||
|
||||
String rangePattern;
|
||||
String approximatelyPattern;
|
||||
|
||||
// For use with SimpleFormatterImpl
|
||||
StringBuilder sb;
|
||||
|
||||
NumberRangeDataSink(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table miscTable = value.getTable();
|
||||
for (int i = 0; miscTable.getKeyAndValue(i, key, value); ++i) {
|
||||
if (key.contentEquals("range") && rangePattern == null) {
|
||||
String pattern = value.getString();
|
||||
rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
|
||||
}
|
||||
if (key.contentEquals("approximately") && approximatelyPattern == null) {
|
||||
String pattern = value.getString();
|
||||
approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getNumberRangeData(
|
||||
ULocale locale,
|
||||
String nsName,
|
||||
NumberRangeFormatterImpl out) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
NumberRangeDataSink sink = new NumberRangeDataSink(sb);
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
sb.append("NumberElements/");
|
||||
sb.append(nsName);
|
||||
sb.append("/miscPatterns");
|
||||
String key = sb.toString();
|
||||
resource.getAllItemsWithFallback(key, sink);
|
||||
|
||||
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
|
||||
|
||||
if (sink.rangePattern == null) {
|
||||
sink.rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments("{0}–{1}", sb, 2, 2);
|
||||
}
|
||||
if (sink.approximatelyPattern == null) {
|
||||
sink.approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments("~{0}", sb, 1, 1);
|
||||
}
|
||||
|
||||
out.fRangePattern = sink.rangePattern;
|
||||
out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
||||
public NumberRangeFormatterImpl(RangeMacroProps macros) {
|
||||
formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
|
||||
: NumberFormatter.withLocale(macros.loc).resolve());
|
||||
formatterImpl2 = new NumberFormatterImpl(macros.formatter2 != null ? macros.formatter2.resolve()
|
||||
: NumberFormatter.withLocale(macros.loc).resolve());
|
||||
fSameFormatters = macros.sameFormatters != 0;
|
||||
fCollapse = macros.collapse != null ? macros.collapse : NumberRangeFormatter.RangeCollapse.AUTO;
|
||||
fIdentityFallback = macros.identityFallback != null ? macros.identityFallback
|
||||
: NumberRangeFormatter.RangeIdentityFallback.APPROXIMATELY;
|
||||
|
||||
// TODO: As of this writing (ICU 63), there is no locale that has different number miscPatterns
|
||||
// based on numbering system. Therefore, data is loaded only from latn. If this changes,
|
||||
// this part of the code should be updated to load from the local numbering system.
|
||||
// The numbering system could come from the one specified in the NumberFormatter passed to
|
||||
// numberFormatterBoth() or similar.
|
||||
// See ICU-20144
|
||||
|
||||
getNumberRangeData(macros.loc, "latn", this);
|
||||
|
||||
// TODO: Get locale from PluralRules instead?
|
||||
fPluralRanges = new StandardPluralRanges(macros.loc);
|
||||
}
|
||||
|
||||
public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
|
||||
NumberStringBuilder string = new NumberStringBuilder();
|
||||
MicroProps micros1 = formatterImpl1.preProcess(quantity1);
|
||||
MicroProps micros2;
|
||||
if (fSameFormatters) {
|
||||
micros2 = formatterImpl1.preProcess(quantity2);
|
||||
} else {
|
||||
micros2 = formatterImpl2.preProcess(quantity2);
|
||||
}
|
||||
|
||||
// If any of the affixes are different, an identity is not possible
|
||||
// and we must use formatRange().
|
||||
// TODO: Write this as MicroProps operator==() ?
|
||||
// TODO: Avoid the redundancy of these equality operations with the
|
||||
// ones in formatRange?
|
||||
if (!micros1.modInner.semanticallyEquivalent(micros2.modInner)
|
||||
|| !micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle)
|
||||
|| !micros1.modOuter.semanticallyEquivalent(micros2.modOuter)) {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
return new FormattedNumberRange(string, quantity1, quantity2, RangeIdentityResult.NOT_EQUAL);
|
||||
}
|
||||
|
||||
// Check for identity
|
||||
RangeIdentityResult identityResult;
|
||||
if (equalBeforeRounding) {
|
||||
identityResult = RangeIdentityResult.EQUAL_BEFORE_ROUNDING;
|
||||
} else if (quantity1.equals(quantity2)) {
|
||||
identityResult = RangeIdentityResult.EQUAL_AFTER_ROUNDING;
|
||||
} else {
|
||||
identityResult = RangeIdentityResult.NOT_EQUAL;
|
||||
}
|
||||
|
||||
// Java does not let us use a constexpr like C++;
|
||||
// we need to expand identity2d calls.
|
||||
switch (identity2d(fIdentityFallback, identityResult)) {
|
||||
case (3 | (2 << 4)): // RANGE, NOT_EQUAL
|
||||
case (3 | (1 << 4)): // RANGE, EQUAL_AFTER_ROUNDING
|
||||
case (3 | (0 << 4)): // RANGE, EQUAL_BEFORE_ROUNDING
|
||||
case (2 | (2 << 4)): // APPROXIMATELY, NOT_EQUAL
|
||||
case (1 | (2 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, NOT_EQUAL
|
||||
case (0 | (2 << 4)): // SINGLE_VALUE, NOT_EQUAL
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
case (2 | (1 << 4)): // APPROXIMATELY, EQUAL_AFTER_ROUNDING
|
||||
case (2 | (0 << 4)): // APPROXIMATELY, EQUAL_BEFORE_ROUNDING
|
||||
case (1 | (1 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_AFTER_ROUNDING
|
||||
formatApproximately(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
case (1 | (0 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
|
||||
case (0 | (1 << 4)): // SINGLE_VALUE, EQUAL_AFTER_ROUNDING
|
||||
case (0 | (0 << 4)): // SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
|
||||
formatSingleValue(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert false;
|
||||
break;
|
||||
}
|
||||
|
||||
return new FormattedNumberRange(string, quantity1, quantity2, identityResult);
|
||||
}
|
||||
|
||||
private void formatSingleValue(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
if (fSameFormatters) {
|
||||
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
|
||||
NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
|
||||
} else {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
if (fSameFormatters) {
|
||||
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
|
||||
length += NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
|
||||
fApproximatelyModifier.apply(string, 0, length);
|
||||
} else {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatRange(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
// modInner is always notation (scientific); collapsable in ALL.
|
||||
// modOuter is always units; collapsable in ALL, AUTO, and UNIT.
|
||||
// modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
|
||||
// Never collapse an outer mod but not an inner mod.
|
||||
boolean collapseOuter, collapseMiddle, collapseInner;
|
||||
switch (fCollapse) {
|
||||
case ALL:
|
||||
case AUTO:
|
||||
case UNIT:
|
||||
{
|
||||
// OUTER MODIFIER
|
||||
collapseOuter = micros1.modOuter.semanticallyEquivalent(micros2.modOuter);
|
||||
|
||||
if (!collapseOuter) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER
|
||||
collapseMiddle = micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle);
|
||||
|
||||
if (!collapseMiddle) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER HEURISTICS
|
||||
// (could disable collapsing of the middle modifier)
|
||||
// The modifiers are equal by this point, so we can look at just one of them.
|
||||
Modifier mm = micros1.modMiddle;
|
||||
if (fCollapse == RangeCollapse.UNIT) {
|
||||
// Only collapse if the modifier is a unit.
|
||||
// TODO: Make a better way to check for a unit?
|
||||
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
|
||||
if (!mm.containsField(NumberFormat.Field.CURRENCY) && !mm.containsField(NumberFormat.Field.PERCENT)) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
} else if (fCollapse == RangeCollapse.AUTO) {
|
||||
// Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
|
||||
if (mm.getCodePointCount() <= 1) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collapseMiddle || fCollapse != RangeCollapse.ALL) {
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// INNER MODIFIER
|
||||
collapseInner = micros1.modInner.semanticallyEquivalent(micros2.modInner);
|
||||
|
||||
// All done checking for collapsability.
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
collapseOuter = false;
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Java doesn't have macros, constexprs, or stack objects.
|
||||
// Use a helper object instead.
|
||||
PrefixInfixSuffixLengthHelper h = new PrefixInfixSuffixLengthHelper();
|
||||
|
||||
SimpleModifier.formatTwoArgPattern(fRangePattern, string, 0, h, null);
|
||||
assert h.lengthInfix > 0;
|
||||
|
||||
// SPACING HEURISTIC
|
||||
// Add spacing unless all modifiers are collapsed.
|
||||
// TODO: add API to control this?
|
||||
// TODO: Use a data-driven heuristic like currency spacing?
|
||||
// TODO: Use Unicode [:whitespace:] instead of PatternProps whitespace? (consider speed implications)
|
||||
{
|
||||
boolean repeatInner = !collapseInner && micros1.modInner.getCodePointCount() > 0;
|
||||
boolean repeatMiddle = !collapseMiddle && micros1.modMiddle.getCodePointCount() > 0;
|
||||
boolean repeatOuter = !collapseOuter && micros1.modOuter.getCodePointCount() > 0;
|
||||
if (repeatInner || repeatMiddle || repeatOuter) {
|
||||
// Add spacing if there is not already spacing
|
||||
if (!PatternProps.isWhiteSpace(string.charAt(h.index1()))) {
|
||||
h.lengthInfix += string.insertCodePoint(h.index1(), '\u0020', null);
|
||||
}
|
||||
if (!PatternProps.isWhiteSpace(string.charAt(h.index2() - 1))) {
|
||||
h.lengthInfix += string.insertCodePoint(h.index2(), '\u0020', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h.length1 += NumberFormatterImpl.writeNumber(micros1, quantity1, string, h.index0());
|
||||
h.length2 += NumberFormatterImpl.writeNumber(micros2, quantity2, string, h.index2());
|
||||
|
||||
// TODO: Support padding?
|
||||
|
||||
if (collapseInner) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
|
||||
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
|
||||
if (collapseMiddle) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
|
||||
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
|
||||
if (collapseOuter) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
|
||||
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
}
|
||||
|
||||
Modifier resolveModifierPlurals(Modifier first, Modifier second) {
|
||||
Modifier.Parameters firstParameters = first.getParameters();
|
||||
if (firstParameters == null) {
|
||||
// No plural form; return a fallback (e.g., the first)
|
||||
return first;
|
||||
}
|
||||
|
||||
Modifier.Parameters secondParameters = second.getParameters();
|
||||
if (secondParameters == null) {
|
||||
// No plural form; return a fallback (e.g., the first)
|
||||
return first;
|
||||
}
|
||||
|
||||
// Get the required plural form from data
|
||||
StandardPlural resultPlural = fPluralRanges.resolve(firstParameters.plural, secondParameters.plural);
|
||||
|
||||
// Get and return the new Modifier
|
||||
assert firstParameters.obj == secondParameters.obj;
|
||||
assert firstParameters.signum == secondParameters.signum;
|
||||
Modifier mod = firstParameters.obj.getModifier(firstParameters.signum, resultPlural);
|
||||
assert mod != null;
|
||||
return mod;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import com.ibm.icu.impl.number.range.RangeMacroProps;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* An abstract base class for specifying settings related to number formatting. This class is implemented by
|
||||
* {@link UnlocalizedNumberRangeFormatter} and {@link LocalizedNumberRangeFormatter}. This class is not intended for
|
||||
* public subclassing.
|
||||
*
|
||||
* @author sffc
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatterSettings<?>> {
|
||||
|
||||
static final int KEY_MACROS = 0; // not used
|
||||
static final int KEY_LOCALE = 1;
|
||||
static final int KEY_FORMATTER_1 = 2;
|
||||
static final int KEY_FORMATTER_2 = 3;
|
||||
static final int KEY_SAME_FORMATTERS = 4;
|
||||
static final int KEY_COLLAPSE = 5;
|
||||
static final int KEY_IDENTITY_FALLBACK = 6;
|
||||
static final int KEY_MAX = 7;
|
||||
|
||||
private final NumberRangeFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
private final Object value;
|
||||
private volatile RangeMacroProps resolvedMacros;
|
||||
|
||||
NumberRangeFormatterSettings(NumberRangeFormatterSettings<?> parent, int key, Object value) {
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the NumberFormatter instance to use for the numbers in the range. The same formatter is applied to both
|
||||
* sides of the range.
|
||||
* <p>
|
||||
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatter
|
||||
* The formatter to use for both numbers in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterBoth(UnlocalizedNumberFormatter formatter) {
|
||||
return (T) create(KEY_SAME_FORMATTERS, true).create(KEY_FORMATTER_1, formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the NumberFormatter instance to use for the first number in the range.
|
||||
* <p>
|
||||
* The NumberFormatter instance must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatterFirst
|
||||
* The formatter to use for the first number in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterFirst(UnlocalizedNumberFormatter formatterFirst) {
|
||||
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_1, formatterFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the NumberFormatter instances to use for the second number in the range.
|
||||
* <p>
|
||||
* The NumberFormatter instance must not have a locale applied yet; the locale specified on the
|
||||
* NumberRangeFormatter will be used.
|
||||
*
|
||||
* @param formatterSecond
|
||||
* The formatter to use for the second number in the range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberFormatter
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterSecond(UnlocalizedNumberFormatter formatterSecond) {
|
||||
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_2, formatterSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aggressiveness of "collapsing" fields across the range separator. Possible values:
|
||||
* <ul>
|
||||
* <li>ALL: "3-5K miles"</li>
|
||||
* <li>UNIT: "3K - 5K miles"</li>
|
||||
* <li>NONE: "3K miles - 5K miles"</li>
|
||||
* <li>AUTO: usually UNIT or NONE, depending on the locale and formatter settings</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The default value is AUTO.
|
||||
*
|
||||
* @param collapse
|
||||
* The collapsing strategy to use for this range.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public T collapse(RangeCollapse collapse) {
|
||||
return create(KEY_COLLAPSE, collapse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior when the two sides of the range are the same. This could happen if the same two numbers are
|
||||
* passed to the formatRange function, or if different numbers are passed to the function but they become the same
|
||||
* after rounding rules are applied. Possible values:
|
||||
* <ul>
|
||||
* <li>SINGLE_VALUE: "5 miles"</li>
|
||||
* <li>APPROXIMATELY_OR_SINGLE_VALUE: "~5 miles" or "5 miles", depending on whether the number was the same before
|
||||
* rounding was applied</li>
|
||||
* <li>APPROXIMATELY: "~5 miles"</li>
|
||||
* <li>RANGE: "5-5 miles" (with collapse=UNIT)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The default value is APPROXIMATELY.
|
||||
*
|
||||
* @param identityFallback
|
||||
* The strategy to use when formatting two numbers that end up being the same.
|
||||
* @return The fluent chain.
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public T identityFallback(RangeIdentityFallback identityFallback) {
|
||||
return create(KEY_IDENTITY_FALLBACK, identityFallback);
|
||||
}
|
||||
|
||||
/* package-protected */ abstract T create(int key, Object value);
|
||||
|
||||
RangeMacroProps resolve() {
|
||||
if (resolvedMacros != null) {
|
||||
return resolvedMacros;
|
||||
}
|
||||
// Although the linked-list fluent storage approach requires this method,
|
||||
// my benchmarks show that linked-list is still faster than a full clone
|
||||
// of a MacroProps object at each step.
|
||||
// TODO: Remove the reference to the parent after the macros are resolved?
|
||||
RangeMacroProps macros = new RangeMacroProps();
|
||||
NumberRangeFormatterSettings<?> current = this;
|
||||
while (current != null) {
|
||||
switch (current.key) {
|
||||
case KEY_MACROS:
|
||||
// ignored for now
|
||||
break;
|
||||
case KEY_LOCALE:
|
||||
if (macros.loc == null) {
|
||||
macros.loc = (ULocale) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_FORMATTER_1:
|
||||
if (macros.formatter1 == null) {
|
||||
macros.formatter1 = (UnlocalizedNumberFormatter) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_FORMATTER_2:
|
||||
if (macros.formatter2 == null) {
|
||||
macros.formatter2 = (UnlocalizedNumberFormatter) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_SAME_FORMATTERS:
|
||||
if (macros.sameFormatters == -1) {
|
||||
macros.sameFormatters = (boolean) current.value ? 1 : 0;
|
||||
}
|
||||
break;
|
||||
case KEY_COLLAPSE:
|
||||
if (macros.collapse == null) {
|
||||
macros.collapse = (RangeCollapse) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_IDENTITY_FALLBACK:
|
||||
if (macros.identityFallback == null) {
|
||||
macros.identityFallback = (RangeIdentityFallback) current.value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown key: " + current.key);
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
// Copy the locale into the children (see touchRangeLocales in C++)
|
||||
if (macros.formatter1 != null) {
|
||||
macros.formatter1.resolve().loc = macros.loc;
|
||||
}
|
||||
if (macros.formatter2 != null) {
|
||||
macros.formatter2.resolve().loc = macros.loc;
|
||||
}
|
||||
resolvedMacros = macros;
|
||||
return macros;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return resolve().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof NumberRangeFormatterSettings)) {
|
||||
return false;
|
||||
}
|
||||
return resolve().equals(((NumberRangeFormatterSettings<?>) other).resolve());
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
import com.ibm.icu.number.Precision.SignificantRounderImpl;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in
|
||||
@ -221,8 +222,10 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
@Override
|
||||
public int getCodePointCount() {
|
||||
// This method is not used for strong modifiers.
|
||||
throw new AssertionError();
|
||||
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
|
||||
// The call site only cares about != 0 and != 1.
|
||||
// Return a very large value so that if this method is used elsewhere, we should notice.
|
||||
return 999;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -231,6 +234,27 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
return doApply(exponent, output, rightIndex);
|
||||
@ -279,8 +303,10 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
|
||||
@Override
|
||||
public int getCodePointCount() {
|
||||
// This method is not used for strong modifiers.
|
||||
throw new AssertionError();
|
||||
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
|
||||
// The call site only cares about != 0 and != 1.
|
||||
// Return a very large value so that if this method is used elsewhere, we should notice.
|
||||
return 999;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -288,5 +314,27 @@ public class ScientificNotation extends Notation implements Cloneable {
|
||||
// Scientific is always strong
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not used for inner modifiers.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parameters getParameters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean semanticallyEquivalent(Modifier other) {
|
||||
if (!(other instanceof ScientificModifier)) {
|
||||
return false;
|
||||
}
|
||||
ScientificModifier _other = (ScientificModifier) other;
|
||||
// TODO: Check for locale symbols and settings as well? Could be less efficient.
|
||||
return exponent == _other.exponent;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user