ICU-11276 Adding NumberRangeFormatter (#87)
Formats number ranges, including currencies and measurement units.
This commit is contained in:
commit
ce92011aff
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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)) {}
|
||||
|
||||
@ -657,9 +659,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 +708,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,
|
||||
NumberStringBuilder& outString, UErrorCode& status) {
|
||||
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,25 +366,23 @@ 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,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
status)));
|
||||
LongNameHandler::forMeasureUnit(
|
||||
macros.locale,
|
||||
macros.unit,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
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)));
|
||||
LongNameHandler::forCurrencyLongNames(
|
||||
macros.locale,
|
||||
currency,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
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,14 +29,14 @@ 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,
|
||||
UErrorCode &status);
|
||||
static int32_t
|
||||
formatStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString,
|
||||
UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Prints only the prefix and suffix; used for DecimalFormat getters.
|
||||
@ -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
|
||||
|
@ -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
|
||||
@ -85,16 +100,33 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
|
||||
* @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
|
||||
*/
|
||||
int32_t
|
||||
formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex, Field field,
|
||||
UErrorCode &status) const;
|
||||
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
|
||||
|
430
icu4c/source/i18n/numrange_fluent.cpp
Normal file
430
icu4c/source/i18n/numrange_fluent.cpp
Normal file
@ -0,0 +1,430 @@
|
||||
// © 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)) {
|
||||
// No additional fields to assign
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(const LNF& other) {
|
||||
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
|
||||
// No additional fields to assign
|
||||
return *this;
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(LNF&& src) U_NOEXCEPT {
|
||||
NFS<LNF>::operator=(static_cast<NFS<LNF>&&>(src));
|
||||
// No additional fields to assign
|
||||
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 */
|
@ -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
|
||||
@ -2457,9 +2456,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 +2477,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
|
||||
|
849
icu4c/source/i18n/unicode/numberrangeformatter.h
Normal file
849
icu4c/source/i18n/unicode/numberrangeformatter.h
Normal file
@ -0,0 +1,849 @@
|
||||
// © 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);
|
||||
|
||||
// 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 */
|
@ -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>
|
||||
|
@ -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,53 @@ 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 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 +325,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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
750
icu4c/source/test/intltest/numbertest_range.cpp
Normal file
750
icu4c/source/test/intltest/numbertest_range.cpp
Normal file
@ -0,0 +1,750 @@
|
||||
// © 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_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::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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// © 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;
|
||||
|
||||
/**
|
||||
* A NumberRangeFormatter that does not yet have a locale. In order to format, a locale must be specified.
|
||||
*
|
||||
* @author sffc
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public class UnlocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter> {
|
||||
|
||||
/** Base constructor; called during startup only. */
|
||||
UnlocalizedNumberRangeFormatter() {
|
||||
super(null, KEY_MACROS, null);
|
||||
}
|
||||
|
||||
UnlocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
|
||||
super(parent, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <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 range formatting.
|
||||
* @return The fluent chain
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public LocalizedNumberRangeFormatter locale(Locale locale) {
|
||||
return new LocalizedNumberRangeFormatter(this, KEY_LOCALE, ULocale.forLocale(locale));
|
||||
}
|
||||
|
||||
/**
|
||||
* ULocale version of the {@link #locale(Locale)} setter above.
|
||||
*
|
||||
* @param locale
|
||||
* The locale to use when loading data for number range formatting.
|
||||
* @return The fluent chain
|
||||
* @see #locale(Locale)
|
||||
* @draft ICU 63
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public LocalizedNumberRangeFormatter locale(ULocale locale) {
|
||||
return new LocalizedNumberRangeFormatter(this, KEY_LOCALE, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
UnlocalizedNumberRangeFormatter create(int key, Object value) {
|
||||
return new UnlocalizedNumberRangeFormatter(this, key, value);
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:92dc0a5ca71ac54537a6c7c42c2f80ccbd3298d9ebf69c3d732199230d5100c6
|
||||
size 12487439
|
||||
oid sha256:6dadae4ae83d956325f4e089327e824bb32dbf75c98e9c2815ffa7521f3505ca
|
||||
size 12512661
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d2308b3498ce1c2b869b60b5a0f7cea6f08aff2fac046ae260e10688c15c60b2
|
||||
oid sha256:b5ffb95eb91501a9f61ee31333e392ff183e0f7dddefb592f04b90fe2ac3490c
|
||||
size 92857
|
||||
|
@ -0,0 +1,726 @@
|
||||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.LocalizedNumberRangeFormatter;
|
||||
import com.ibm.icu.number.Notation;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.number.NumberRangeFormatter;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.number.Precision;
|
||||
import com.ibm.icu.number.UnlocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.UnlocalizedNumberRangeFormatter;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* @author sffc
|
||||
*
|
||||
*/
|
||||
public class NumberRangeFormatterTest {
|
||||
|
||||
private static final Currency USD = Currency.getInstance("USD");
|
||||
private static final Currency GBP = Currency.getInstance("GBP");
|
||||
private static final Currency PTE = Currency.getInstance("PTE");
|
||||
|
||||
@Test
|
||||
public void testSanity() {
|
||||
LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter.withLocale(ULocale.US);
|
||||
LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter.with().locale(ULocale.US);
|
||||
LocalizedNumberRangeFormatter lnrf3 = NumberRangeFormatter.withLocale(Locale.US);
|
||||
LocalizedNumberRangeFormatter lnrf4 = NumberRangeFormatter.with().locale(Locale.US);
|
||||
assertEquals("Formatters should be equal 1", lnrf1, lnrf2);
|
||||
assertEquals("Formatters should be equal 2", lnrf2, lnrf3);
|
||||
assertEquals("Formatters should be equal 3", lnrf3, lnrf4);
|
||||
assertEquals("Formatters should have same behavior 1", lnrf1.formatRange(4, 6), lnrf2.formatRange(4, 6));
|
||||
assertEquals("Formatters should have same behavior 2", lnrf2.formatRange(4, 6), lnrf3.formatRange(4, 6));
|
||||
assertEquals("Formatters should have same behavior 3", lnrf3.formatRange(4, 6), lnrf4.formatRange(4, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasic() {
|
||||
assertFormatRange(
|
||||
"Basic",
|
||||
NumberRangeFormatter.with(),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"~5",
|
||||
"0–3",
|
||||
"~0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"~5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic with units",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic with different units",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(NumberFormatter.with().unit(MeasureUnit.METER))
|
||||
.numberFormatterSecond(NumberFormatter.with().unit(MeasureUnit.KILOMETER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 km",
|
||||
"5 m – 5 km",
|
||||
"5 m – 5 km",
|
||||
"0 m – 3 km",
|
||||
"0 m – 0 km",
|
||||
"3 m – 3,000 km",
|
||||
"3,000 m – 5,000 km",
|
||||
"4,999 m – 5,001 km",
|
||||
"5,000 m – 5,000 km",
|
||||
"5,000 m – 5,000,000 km");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic long unit",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 meters",
|
||||
"~5 meters",
|
||||
"~5 meters",
|
||||
"0–3 meters",
|
||||
"~0 meters",
|
||||
"3–3,000 meters",
|
||||
"3,000–5,000 meters",
|
||||
"4,999–5,001 meters",
|
||||
"~5,000 meters",
|
||||
"5,000–5,000,000 meters");
|
||||
|
||||
assertFormatRange(
|
||||
"Non-English locale and unit",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME)),
|
||||
new ULocale("fr-FR"),
|
||||
"1–5 degrés Fahrenheit",
|
||||
"~5 degrés Fahrenheit",
|
||||
"~5 degrés Fahrenheit",
|
||||
"0–3 degrés Fahrenheit",
|
||||
"~0 degré Fahrenheit",
|
||||
"3–3 000 degrés Fahrenheit",
|
||||
"3 000–5 000 degrés Fahrenheit",
|
||||
"4 999–5 001 degrés Fahrenheit",
|
||||
"~5 000 degrés Fahrenheit",
|
||||
"5 000–5 000 000 degrés Fahrenheit");
|
||||
|
||||
assertFormatRange(
|
||||
"Locale with custom range separator",
|
||||
NumberRangeFormatter.with(),
|
||||
new ULocale("ja"),
|
||||
"1~5",
|
||||
"~5",
|
||||
"~5",
|
||||
"0~3",
|
||||
"~0",
|
||||
"3~3,000",
|
||||
"3,000~5,000",
|
||||
"4,999~5,001",
|
||||
"~5,000",
|
||||
"5,000~5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Locale that already has spaces around range separator",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.KELVIN)),
|
||||
new ULocale("hr"),
|
||||
"1 K – 5 K",
|
||||
"~5 K",
|
||||
"~5 K",
|
||||
"0 K – 3 K",
|
||||
"~0 K",
|
||||
"3 K – 3.000 K",
|
||||
"3.000 K – 5.000 K",
|
||||
"4.999 K – 5.001 K",
|
||||
"~5.000 K",
|
||||
"5.000 K – 5.000.000 K");
|
||||
|
||||
assertFormatRange(
|
||||
"Locale with custom numbering system and no plural ranges data",
|
||||
NumberRangeFormatter.with(),
|
||||
new ULocale("shn@numbers=beng"),
|
||||
// 012459 = ০১৩৪৫৯
|
||||
"১–৫",
|
||||
"~৫",
|
||||
"~৫",
|
||||
"০–৩",
|
||||
"~০",
|
||||
"৩–৩,০০০",
|
||||
"৩,০০০–৫,০০০",
|
||||
"৪,৯৯৯–৫,০০১",
|
||||
"~৫,০০০",
|
||||
"৫,০০০–৫,০০০,০০০");
|
||||
|
||||
assertFormatRange(
|
||||
"Portuguese currency",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(PTE)),
|
||||
new ULocale("pt-PT"),
|
||||
"1$00 - 5$00 \u200B",
|
||||
"~5$00 \u200B",
|
||||
"~5$00 \u200B",
|
||||
"0$00 - 3$00 \u200B",
|
||||
"~0$00 \u200B",
|
||||
"3$00 - 3000$00 \u200B",
|
||||
"3000$00 - 5000$00 \u200B",
|
||||
"4999$00 - 5001$00 \u200B",
|
||||
"~5000$00 \u200B",
|
||||
"5000$00 - 5,000,000$00 \u200B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollapse() {
|
||||
assertFormatRange(
|
||||
"Default collapse on currency (default rounding)",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD)),
|
||||
new ULocale("en-us"),
|
||||
"$1.00 – $5.00",
|
||||
"~$5.00",
|
||||
"~$5.00",
|
||||
"$0.00 – $3.00",
|
||||
"~$0.00",
|
||||
"$3.00 – $3,000.00",
|
||||
"$3,000.00 – $5,000.00",
|
||||
"$4,999.00 – $5,001.00",
|
||||
"~$5,000.00",
|
||||
"$5,000.00 – $5,000,000.00");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1 – $5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0 – $3",
|
||||
"~$0",
|
||||
"$3 – $3,000",
|
||||
"$3,000 – $5,000",
|
||||
"$4,999 – $5,001",
|
||||
"~$5,000",
|
||||
"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1 – $5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0 – $3",
|
||||
"~$0",
|
||||
"$3 – $3,000",
|
||||
"$3,000 – $5,000",
|
||||
"$4,999 – $5,001",
|
||||
"~$5,000",
|
||||
"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1–5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0–3",
|
||||
"~$0",
|
||||
"$3–3,000",
|
||||
"$3,000–5,000",
|
||||
"$4,999–5,001",
|
||||
"~$5,000",
|
||||
"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1–5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0–3",
|
||||
"~$0",
|
||||
"$3–3,000",
|
||||
"$3,000–5,000",
|
||||
"$4,999–5,001",
|
||||
"~$5,000",
|
||||
"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1 – GBP 5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0 – GBP 3",
|
||||
"~GBP 0",
|
||||
"GBP 3 – GBP 3,000",
|
||||
"GBP 3,000 – GBP 5,000",
|
||||
"GBP 4,999 – GBP 5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000 – GBP 5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
// Default collapse on measurement unit is in testBasic()
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0 m – 3 m",
|
||||
"~0 m",
|
||||
"3 m – 3,000 m",
|
||||
"3,000 m – 5,000 m",
|
||||
"4,999 m – 5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000 m – 5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse, long-form compact notation",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactLong())),
|
||||
new ULocale("de-CH"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"~5",
|
||||
"0–3",
|
||||
"~0",
|
||||
"3–3 Tausend",
|
||||
"3–5 Tausend",
|
||||
"~5 Tausend",
|
||||
"~5 Tausend",
|
||||
"5 Tausend – 5 Millionen");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse, long-form compact notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactLong())),
|
||||
new ULocale("de-CH"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"~5",
|
||||
"0–3",
|
||||
"~0",
|
||||
"3–3 Tausend",
|
||||
"3 Tausend – 5 Tausend",
|
||||
"~5 Tausend",
|
||||
"~5 Tausend",
|
||||
"5 Tausend – 5 Millionen");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3K – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0 m – 3 m",
|
||||
"~0 m",
|
||||
"3 m – 3K m",
|
||||
"3K m – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K m – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3K – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3–5K m", // this one is the key use case for ALL
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on scientific notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.scientific())),
|
||||
new ULocale("en-us"),
|
||||
"1E0 – 5E0",
|
||||
"~5E0",
|
||||
"~5E0",
|
||||
"0E0 – 3E0",
|
||||
"~0E0",
|
||||
"3E0 – 3E3",
|
||||
"3E3 – 5E3",
|
||||
"4.999E3 – 5.001E3",
|
||||
"~5E3",
|
||||
"5E3 – 5E6");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on scientific notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.scientific())),
|
||||
new ULocale("en-us"),
|
||||
"1–5E0",
|
||||
"~5E0",
|
||||
"~5E0",
|
||||
"0–3E0",
|
||||
"~0E0",
|
||||
"3E0 – 3E3",
|
||||
"3–5E3",
|
||||
"4.999–5.001E3",
|
||||
"~5E3",
|
||||
"5E3 – 5E6");
|
||||
|
||||
// TODO: Test compact currency?
|
||||
// The code is not smart enough to differentiate the notation from the unit.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentity() {
|
||||
assertFormatRange(
|
||||
"Identity fallback Range",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.RANGE),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"5–5",
|
||||
"5–5",
|
||||
"0–3",
|
||||
"0–0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"5,000–5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Identity fallback Approximately or Single Value",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Identity fallback Single Value",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.SINGLE_VALUE),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Identity fallback Approximately or Single Value with compact notation",
|
||||
NumberRangeFormatter.with()
|
||||
.identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort())),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3K",
|
||||
"3K – 5K",
|
||||
"~5K",
|
||||
"5K",
|
||||
"5K – 5M");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentFormatters() {
|
||||
assertFormatRange(
|
||||
"Different rounding rules",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(NumberFormatter.with().precision(Precision.integer()))
|
||||
.numberFormatterSecond(NumberFormatter.with().precision(Precision.fixedDigits(2))),
|
||||
new ULocale("en-us"),
|
||||
"1–5.0",
|
||||
"5–5.0",
|
||||
"5–5.0",
|
||||
"0–3.0",
|
||||
"0–0.0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,000",
|
||||
"5,000–5,000", // TODO: Should this one be ~5,000?
|
||||
"5,000–5,000,000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlurals() {
|
||||
// Locale sl has interesting plural forms:
|
||||
// GBP{
|
||||
// one{"britanski funt"}
|
||||
// two{"britanska funta"}
|
||||
// few{"britanski funti"}
|
||||
// other{"britanskih funtov"}
|
||||
// }
|
||||
ULocale locale = new ULocale("sl");
|
||||
|
||||
UnlocalizedNumberFormatter unf = NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.FULL_NAME)
|
||||
.precision(Precision.integer());
|
||||
LocalizedNumberFormatter lnf = unf.locale(locale);
|
||||
|
||||
// For comparison, run the non-range version of the formatter
|
||||
assertEquals(Integer.toString(1), "1 britanski funt", lnf.format(1).toString());
|
||||
assertEquals(Integer.toString(2), "2 britanska funta", lnf.format(2).toString());
|
||||
assertEquals(Integer.toString(3), "3 britanski funti", lnf.format(3).toString());
|
||||
assertEquals(Integer.toString(5), "5 britanskih funtov", lnf.format(5).toString());
|
||||
|
||||
LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(unf)
|
||||
.identityFallback(RangeIdentityFallback.RANGE)
|
||||
.locale(locale);
|
||||
|
||||
Object[][] cases = new Object[][] {
|
||||
{1, 1, "1–1 britanski funti"}, // one + one -> few
|
||||
{1, 2, "1–2 britanska funta"}, // one + two -> two
|
||||
{1, 3, "1–3 britanski funti"}, // one + few -> few
|
||||
{1, 5, "1–5 britanskih funtov"}, // one + other -> other
|
||||
{2, 1, "2–1 britanski funti"}, // two + one -> few
|
||||
{2, 2, "2–2 britanska funta"}, // two + two -> two
|
||||
{2, 3, "2–3 britanski funti"}, // two + few -> few
|
||||
{2, 5, "2–5 britanskih funtov"}, // two + other -> other
|
||||
{3, 1, "3–1 britanski funti"}, // few + one -> few
|
||||
{3, 2, "3–2 britanska funta"}, // few + two -> two
|
||||
{3, 3, "3–3 britanski funti"}, // few + few -> few
|
||||
{3, 5, "3–5 britanskih funtov"}, // few + other -> other
|
||||
{5, 1, "5–1 britanski funti"}, // other + one -> few
|
||||
{5, 2, "5–2 britanska funta"}, // other + two -> two
|
||||
{5, 3, "5–3 britanski funti"}, // other + few -> few
|
||||
{5, 5, "5–5 britanskih funtov"}, // other + other -> other
|
||||
};
|
||||
for (Object[] cas : cases) {
|
||||
int first = (Integer) cas[0];
|
||||
int second = (Integer) cas[1];
|
||||
String expected = (String) cas[2];
|
||||
String message = Integer.toString(first) + " " + Integer.toString(second);
|
||||
String actual = lnrf.formatRange(first, second).toString();
|
||||
assertEquals(message, expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
static void assertFormatRange(
|
||||
String message,
|
||||
UnlocalizedNumberRangeFormatter f,
|
||||
ULocale locale,
|
||||
String expected_10_50,
|
||||
String expected_49_51,
|
||||
String expected_50_50,
|
||||
String expected_00_30,
|
||||
String expected_00_00,
|
||||
String expected_30_3K,
|
||||
String expected_30K_50K,
|
||||
String expected_49K_51K,
|
||||
String expected_50K_50K,
|
||||
String 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);
|
||||
}
|
||||
|
||||
private static void assertFormattedRangeEquals(String message, LocalizedNumberRangeFormatter l, Number first,
|
||||
Number second, String expected) {
|
||||
String actual = l.formatRange(first, second).toString();
|
||||
assertEquals(message + ": " + first + ", " + second, expected, actual);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user