ICU-20700 reimplement acceptLanguage() using the LocaleMatcher; replace older accept-language-string parsing by LocalePriorityList
This commit is contained in:
parent
3edff03393
commit
d2ea4513dc
@ -12,6 +12,7 @@
|
||||
#include "unicode/localematcher.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uloc.h"
|
||||
#include "unicode/uobject.h"
|
||||
#include "cstring.h"
|
||||
#include "localeprioritylist.h"
|
||||
@ -20,6 +21,7 @@
|
||||
#include "lsr.h"
|
||||
#include "uassert.h"
|
||||
#include "uhash.h"
|
||||
#include "ustr_imp.h"
|
||||
#include "uvector.h"
|
||||
|
||||
#define UND_LSR LSR("und", "", "", LSR::EXPLICIT_LSR)
|
||||
@ -688,4 +690,97 @@ double LocaleMatcher::internalMatch(const Locale &desired, const Locale &support
|
||||
|
||||
U_NAMESPACE_END
|
||||
|
||||
// uloc_acceptLanguage() --------------------------------------------------- ***
|
||||
|
||||
U_NAMESPACE_USE
|
||||
|
||||
namespace {
|
||||
|
||||
class LocaleFromTag {
|
||||
public:
|
||||
LocaleFromTag() : locale(Locale::getRoot()) {}
|
||||
const Locale &operator()(const char *tag) { return locale = Locale(tag); }
|
||||
|
||||
private:
|
||||
// Store the locale in the converter, rather than return a reference to a temporary,
|
||||
// or a value which could go out of scope with the caller's reference to it.
|
||||
Locale locale;
|
||||
};
|
||||
|
||||
int32_t acceptLanguage(UEnumeration &supportedLocales, Locale::Iterator &desiredLocales,
|
||||
char *dest, int32_t capacity, UAcceptResult *acceptResult,
|
||||
UErrorCode &errorCode) {
|
||||
if (U_FAILURE(errorCode)) { return 0; }
|
||||
LocaleMatcher::Builder builder;
|
||||
const char *locString;
|
||||
while ((locString = uenum_next(&supportedLocales, nullptr, &errorCode)) != nullptr) {
|
||||
Locale loc(locString);
|
||||
if (loc.isBogus()) {
|
||||
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return 0;
|
||||
}
|
||||
builder.addSupportedLocale(loc);
|
||||
}
|
||||
LocaleMatcher matcher = builder.build(errorCode);
|
||||
LocaleMatcher::Result result = matcher.getBestMatchResult(desiredLocales, errorCode);
|
||||
if (U_FAILURE(errorCode)) { return 0; }
|
||||
if (result.getDesiredIndex() >= 0) {
|
||||
if (acceptResult != nullptr) {
|
||||
*acceptResult = *result.getDesiredLocale() == *result.getSupportedLocale() ?
|
||||
ULOC_ACCEPT_VALID : ULOC_ACCEPT_FALLBACK;
|
||||
}
|
||||
const char *bestStr = result.getSupportedLocale()->getName();
|
||||
int32_t bestLength = (int32_t)uprv_strlen(bestStr);
|
||||
if (bestLength <= capacity) {
|
||||
uprv_memcpy(dest, bestStr, bestLength);
|
||||
}
|
||||
return u_terminateChars(dest, capacity, bestLength, &errorCode);
|
||||
} else {
|
||||
if (acceptResult != nullptr) {
|
||||
*acceptResult = ULOC_ACCEPT_FAILED;
|
||||
}
|
||||
return u_terminateChars(dest, capacity, 0, &errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
uloc_acceptLanguage(char *result, int32_t resultAvailable,
|
||||
UAcceptResult *outResult,
|
||||
const char **acceptList, int32_t acceptListCount,
|
||||
UEnumeration *availableLocales,
|
||||
UErrorCode *status) {
|
||||
if (U_FAILURE(*status)) { return 0; }
|
||||
if ((result == nullptr ? resultAvailable != 0 : resultAvailable < 0) ||
|
||||
(acceptList == nullptr ? acceptListCount != 0 : acceptListCount < 0) ||
|
||||
availableLocales == nullptr) {
|
||||
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return 0;
|
||||
}
|
||||
LocaleFromTag converter;
|
||||
Locale::ConvertingIterator<const char **, LocaleFromTag> desiredLocales(
|
||||
acceptList, acceptList + acceptListCount, converter);
|
||||
return acceptLanguage(*availableLocales, desiredLocales,
|
||||
result, resultAvailable, outResult, *status);
|
||||
}
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable,
|
||||
UAcceptResult *outResult,
|
||||
const char *httpAcceptLanguage,
|
||||
UEnumeration *availableLocales,
|
||||
UErrorCode *status) {
|
||||
if (U_FAILURE(*status)) { return 0; }
|
||||
if ((result == nullptr ? resultAvailable != 0 : resultAvailable < 0) ||
|
||||
httpAcceptLanguage == nullptr || availableLocales == nullptr) {
|
||||
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return 0;
|
||||
}
|
||||
LocalePriorityList list(httpAcceptLanguage, *status);
|
||||
LocalePriorityList::Iterator desiredLocales = list.iterator();
|
||||
return acceptLanguage(*availableLocales, desiredLocales,
|
||||
result, resultAvailable, outResult, *status);
|
||||
}
|
||||
|
||||
#endif // __LOCMATCHER_H__
|
||||
|
@ -133,7 +133,7 @@ LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) {
|
||||
if (U_FAILURE(errorCode)) { return; }
|
||||
Locale locale = Locale(tag.data());
|
||||
if (locale.isBogus()) {
|
||||
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
||||
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
int32_t weight = WEIGHT_ONE;
|
||||
|
@ -2127,301 +2127,6 @@ uloc_getISOCountries()
|
||||
return COUNTRIES;
|
||||
}
|
||||
|
||||
|
||||
/* this function to be moved into cstring.c later */
|
||||
static char gDecimal = 0;
|
||||
|
||||
static /* U_CAPI */
|
||||
double
|
||||
/* U_EXPORT2 */
|
||||
_uloc_strtod(const char *start, char **end) {
|
||||
char *decimal;
|
||||
char *myEnd;
|
||||
char buf[30];
|
||||
double rv;
|
||||
if (!gDecimal) {
|
||||
char rep[5];
|
||||
/* For machines that decide to change the decimal on you,
|
||||
and try to be too smart with localization.
|
||||
This normally should be just a '.'. */
|
||||
sprintf(rep, "%+1.1f", 1.0);
|
||||
gDecimal = rep[2];
|
||||
}
|
||||
|
||||
if(gDecimal == '.') {
|
||||
return uprv_strtod(start, end); /* fall through to OS */
|
||||
} else {
|
||||
uprv_strncpy(buf, start, 29);
|
||||
buf[29]=0;
|
||||
decimal = uprv_strchr(buf, '.');
|
||||
if(decimal) {
|
||||
*decimal = gDecimal;
|
||||
} else {
|
||||
return uprv_strtod(start, end); /* no decimal point */
|
||||
}
|
||||
rv = uprv_strtod(buf, &myEnd);
|
||||
if(end) {
|
||||
*end = (char*)(start+(myEnd-buf)); /* cast away const (to follow uprv_strtod API.) */
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
float q;
|
||||
int32_t dummy; /* to avoid uninitialized memory copy from qsort */
|
||||
char locale[ULOC_FULLNAME_CAPACITY+1];
|
||||
} _acceptLangItem;
|
||||
|
||||
static int32_t U_CALLCONV
|
||||
uloc_acceptLanguageCompare(const void * /*context*/, const void *a, const void *b)
|
||||
{
|
||||
const _acceptLangItem *aa = (const _acceptLangItem*)a;
|
||||
const _acceptLangItem *bb = (const _acceptLangItem*)b;
|
||||
|
||||
int32_t rc = 0;
|
||||
if(bb->q < aa->q) {
|
||||
rc = -1; /* A > B */
|
||||
} else if(bb->q > aa->q) {
|
||||
rc = 1; /* A < B */
|
||||
} else {
|
||||
rc = 0; /* A = B */
|
||||
}
|
||||
|
||||
if(rc==0) {
|
||||
rc = uprv_stricmp(aa->locale, bb->locale);
|
||||
}
|
||||
|
||||
#if defined(ULOC_DEBUG)
|
||||
/* fprintf(stderr, "a:[%s:%g], b:[%s:%g] -> %d\n",
|
||||
aa->locale, aa->q,
|
||||
bb->locale, bb->q,
|
||||
rc);*/
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53
|
||||
*/
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult *outResult,
|
||||
const char *httpAcceptLanguage,
|
||||
UEnumeration* availableLocales,
|
||||
UErrorCode *status)
|
||||
{
|
||||
MaybeStackArray<_acceptLangItem, 4> items; // Struct for collecting items.
|
||||
char tmp[ULOC_FULLNAME_CAPACITY +1];
|
||||
int32_t n = 0;
|
||||
const char *itemEnd;
|
||||
const char *paramEnd;
|
||||
const char *s;
|
||||
const char *t;
|
||||
int32_t res;
|
||||
int32_t i;
|
||||
int32_t l = (int32_t)uprv_strlen(httpAcceptLanguage);
|
||||
|
||||
if(U_FAILURE(*status)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(s=httpAcceptLanguage;s&&*s;) {
|
||||
while(isspace(*s)) /* eat space at the beginning */
|
||||
s++;
|
||||
itemEnd=uprv_strchr(s,',');
|
||||
paramEnd=uprv_strchr(s,';');
|
||||
if(!itemEnd) {
|
||||
itemEnd = httpAcceptLanguage+l; /* end of string */
|
||||
}
|
||||
if(paramEnd && paramEnd<itemEnd) {
|
||||
/* semicolon (;) is closer than end (,) */
|
||||
t = paramEnd+1;
|
||||
if(*t=='q') {
|
||||
t++;
|
||||
}
|
||||
while(isspace(*t)) {
|
||||
t++;
|
||||
}
|
||||
if(*t=='=') {
|
||||
t++;
|
||||
}
|
||||
while(isspace(*t)) {
|
||||
t++;
|
||||
}
|
||||
items[n].q = (float)_uloc_strtod(t,NULL);
|
||||
} else {
|
||||
/* no semicolon - it's 1.0 */
|
||||
items[n].q = 1.0f;
|
||||
paramEnd = itemEnd;
|
||||
}
|
||||
items[n].dummy=0;
|
||||
/* eat spaces prior to semi */
|
||||
for(t=(paramEnd-1);(paramEnd>s)&&isspace(*t);t--)
|
||||
;
|
||||
int32_t slen = static_cast<int32_t>(((t+1)-s));
|
||||
if(slen > ULOC_FULLNAME_CAPACITY) {
|
||||
*status = U_BUFFER_OVERFLOW_ERROR;
|
||||
return -1; // too big
|
||||
}
|
||||
uprv_strncpy(items[n].locale, s, slen);
|
||||
items[n].locale[slen]=0; // terminate
|
||||
int32_t clen = uloc_canonicalize(items[n].locale, tmp, UPRV_LENGTHOF(tmp)-1, status);
|
||||
if(U_FAILURE(*status)) return -1;
|
||||
if((clen!=slen) || (uprv_strncmp(items[n].locale, tmp, slen))) {
|
||||
// canonicalization had an effect- copy back
|
||||
uprv_strncpy(items[n].locale, tmp, clen);
|
||||
items[n].locale[clen] = 0; // terminate
|
||||
}
|
||||
#if defined(ULOC_DEBUG)
|
||||
/*fprintf(stderr,"%d: s <%s> q <%g>\n", n, j[n].locale, j[n].q);*/
|
||||
#endif
|
||||
n++;
|
||||
s = itemEnd;
|
||||
while(*s==',') { /* eat duplicate commas */
|
||||
s++;
|
||||
}
|
||||
if(n>=items.getCapacity()) { // If we need more items
|
||||
if(NULL == items.resize(items.getCapacity()*2, items.getCapacity())) {
|
||||
*status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return -1;
|
||||
}
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr,"malloced at size %d\n", items.getCapacity());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
uprv_sortArray(items.getAlias(), n, sizeof(items[0]), uloc_acceptLanguageCompare, NULL, TRUE, status);
|
||||
if (U_FAILURE(*status)) {
|
||||
return -1;
|
||||
}
|
||||
LocalMemory<const char*> strs(NULL);
|
||||
if (strs.allocateInsteadAndReset(n) == NULL) {
|
||||
*status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return -1;
|
||||
}
|
||||
for(i=0;i<n;i++) {
|
||||
#if defined(ULOC_DEBUG)
|
||||
/*fprintf(stderr,"%d: s <%s> q <%g>\n", i, j[i].locale, j[i].q);*/
|
||||
#endif
|
||||
strs[i]=items[i].locale;
|
||||
}
|
||||
res = uloc_acceptLanguage(result, resultAvailable, outResult,
|
||||
strs.getAlias(), n, availableLocales, status);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
uloc_acceptLanguage(char *result, int32_t resultAvailable,
|
||||
UAcceptResult *outResult, const char **acceptList,
|
||||
int32_t acceptListCount,
|
||||
UEnumeration* availableLocales,
|
||||
UErrorCode *status)
|
||||
{
|
||||
int32_t i,j;
|
||||
int32_t len;
|
||||
int32_t maxLen=0;
|
||||
char tmp[ULOC_FULLNAME_CAPACITY+1];
|
||||
const char *l;
|
||||
char **fallbackList;
|
||||
if(U_FAILURE(*status)) {
|
||||
return -1;
|
||||
}
|
||||
fallbackList = static_cast<char **>(uprv_malloc((size_t)(sizeof(fallbackList[0])*acceptListCount)));
|
||||
if(fallbackList==NULL) {
|
||||
*status = U_MEMORY_ALLOCATION_ERROR;
|
||||
return -1;
|
||||
}
|
||||
for(i=0;i<acceptListCount;i++) {
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr,"%02d: %s\n", i, acceptList[i]);
|
||||
#endif
|
||||
while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr," %s\n", l);
|
||||
#endif
|
||||
len = (int32_t)uprv_strlen(l);
|
||||
if(!uprv_strcmp(acceptList[i], l)) {
|
||||
if(outResult) {
|
||||
*outResult = ULOC_ACCEPT_VALID;
|
||||
}
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr, "MATCH! %s\n", l);
|
||||
#endif
|
||||
if(len>0) {
|
||||
uprv_strncpy(result, l, uprv_min(len, resultAvailable));
|
||||
}
|
||||
for(j=0;j<i;j++) {
|
||||
uprv_free(fallbackList[j]);
|
||||
}
|
||||
uprv_free(fallbackList);
|
||||
return u_terminateChars(result, resultAvailable, len, status);
|
||||
}
|
||||
if(len>maxLen) {
|
||||
maxLen = len;
|
||||
}
|
||||
}
|
||||
uenum_reset(availableLocales, status);
|
||||
/* save off parent info */
|
||||
if(uloc_getParent(acceptList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) {
|
||||
fallbackList[i] = uprv_strdup(tmp);
|
||||
} else {
|
||||
fallbackList[i]=0;
|
||||
}
|
||||
}
|
||||
|
||||
for(maxLen--;maxLen>0;maxLen--) {
|
||||
for(i=0;i<acceptListCount;i++) {
|
||||
if(fallbackList[i] && ((int32_t)uprv_strlen(fallbackList[i])==maxLen)) {
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr,"Try: [%s]", fallbackList[i]);
|
||||
#endif
|
||||
while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr," %s\n", l);
|
||||
#endif
|
||||
len = (int32_t)uprv_strlen(l);
|
||||
if(!uprv_strcmp(fallbackList[i], l)) {
|
||||
if(outResult) {
|
||||
*outResult = ULOC_ACCEPT_FALLBACK;
|
||||
}
|
||||
#if defined(ULOC_DEBUG)
|
||||
fprintf(stderr, "fallback MATCH! %s\n", l);
|
||||
#endif
|
||||
if(len>0) {
|
||||
uprv_strncpy(result, l, uprv_min(len, resultAvailable));
|
||||
}
|
||||
for(j=0;j<acceptListCount;j++) {
|
||||
uprv_free(fallbackList[j]);
|
||||
}
|
||||
uprv_free(fallbackList);
|
||||
return u_terminateChars(result, resultAvailable, len, status);
|
||||
}
|
||||
}
|
||||
uenum_reset(availableLocales, status);
|
||||
|
||||
if(uloc_getParent(fallbackList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) {
|
||||
uprv_free(fallbackList[i]);
|
||||
fallbackList[i] = uprv_strdup(tmp);
|
||||
} else {
|
||||
uprv_free(fallbackList[i]);
|
||||
fallbackList[i]=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(outResult) {
|
||||
*outResult = ULOC_ACCEPT_FAILED;
|
||||
}
|
||||
}
|
||||
for(i=0;i<acceptListCount;i++) {
|
||||
uprv_free(fallbackList[i]);
|
||||
}
|
||||
uprv_free(fallbackList);
|
||||
return -1;
|
||||
}
|
||||
|
||||
U_CAPI const char* U_EXPORT2
|
||||
uloc_toUnicodeLocaleKey(const char* keyword)
|
||||
{
|
||||
|
@ -2988,30 +2988,33 @@ static void TestAcceptLanguage(void) {
|
||||
} tests[] = {
|
||||
/*0*/{ 0, NULL, "mt_MT", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
|
||||
/*1*/{ 1, NULL, "en", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
|
||||
/*2*/{ 2, NULL, "en", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR},
|
||||
/*2*/{ 2, NULL, "en_GB", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR},
|
||||
/*3*/{ 3, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR},
|
||||
/*4*/{ 4, NULL, "es", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
|
||||
/*5*/{ 5, NULL, "en", ULOC_ACCEPT_VALID, U_ZERO_ERROR}, /* XF */
|
||||
/*5*/{ 5, NULL, "zh", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
|
||||
/*6*/{ 6, NULL, "ja", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
|
||||
/*7*/{ 7, NULL, "zh", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
|
||||
/*8*/{ 8, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR }, /* */
|
||||
/*9*/{ 9, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR }, /* */
|
||||
/*10*/{10, NULL, "", ULOC_ACCEPT_FAILED, U_BUFFER_OVERFLOW_ERROR }, /* */
|
||||
/*11*/{11, NULL, "", ULOC_ACCEPT_FAILED, U_BUFFER_OVERFLOW_ERROR }, /* */
|
||||
/*8*/{ 8, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
|
||||
/*9*/{ 9, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
|
||||
/*10*/{10, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
|
||||
/*11*/{11, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
|
||||
};
|
||||
const int32_t numTests = UPRV_LENGTHOF(tests);
|
||||
static const char *http[] = {
|
||||
/*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=.01",
|
||||
/*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, "
|
||||
"iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, "
|
||||
"es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, "
|
||||
"nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=0.01",
|
||||
/*1*/ "ja;q=0.5, en;q=0.8, tlh",
|
||||
/*2*/ "en-wf, de-lx;q=0.8",
|
||||
/*3*/ "mga-ie;q=0.9, tlh",
|
||||
/*4*/ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xx-yy;q=.1, "
|
||||
/*3*/ "mga-ie;q=0.9, sux",
|
||||
/*4*/ "xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
|
||||
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xx-yy;q=0.1, "
|
||||
"es",
|
||||
/*5*/ "zh-xx;q=0.9, en;q=0.6",
|
||||
/*6*/ "ja-JA",
|
||||
@ -3042,12 +3045,17 @@ static void TestAcceptLanguage(void) {
|
||||
|
||||
available = ures_openAvailableLocales(tests[i].icuSet, &status);
|
||||
tmp[0]=0;
|
||||
rc = uloc_acceptLanguageFromHTTP(tmp, 199, &outResult, http[tests[i].httpSet], available, &status);
|
||||
rc = uloc_acceptLanguageFromHTTP(tmp, 199, &outResult,
|
||||
http[tests[i].httpSet], available, &status);
|
||||
(void)rc; /* Suppress set but not used warning. */
|
||||
uenum_close(available);
|
||||
log_verbose(" got %s, %s [%s]\n", tmp[0]?tmp:"(EMPTY)", acceptResult(outResult), u_errorName(status));
|
||||
log_verbose(" got %s, %s [%s]\n",
|
||||
tmp[0]?tmp:"(EMPTY)", acceptResult(outResult), u_errorName(status));
|
||||
if(status != tests[i].expectStatus) {
|
||||
log_err_status(status, "FAIL: expected status %s but got %s\n", u_errorName(tests[i].expectStatus), u_errorName(status));
|
||||
log_err_status(status,
|
||||
"FAIL: expected status %s but got %s\n",
|
||||
u_errorName(tests[i].expectStatus),
|
||||
u_errorName(status));
|
||||
} else if(U_SUCCESS(tests[i].expectStatus)) {
|
||||
/* don't check content if expected failure */
|
||||
if(outResult != tests[i].res) {
|
||||
@ -3055,10 +3063,13 @@ static void TestAcceptLanguage(void) {
|
||||
acceptResult( tests[i].res),
|
||||
acceptResult( outResult));
|
||||
log_info("test #%d: http[%s], ICU[%s], expect %s, %s\n",
|
||||
i, http[tests[i].httpSet], tests[i].icuSet, tests[i].expect,acceptResult(tests[i].res));
|
||||
i, http[tests[i].httpSet], tests[i].icuSet,
|
||||
tests[i].expect,acceptResult(tests[i].res));
|
||||
}
|
||||
if((outResult>0)&&uprv_strcmp(tmp, tests[i].expect)) {
|
||||
log_err_status(status, "FAIL: #%d: expected %s but got %s\n", i, tests[i].expect, tmp);
|
||||
log_err_status(status,
|
||||
"FAIL: #%d: expected %s but got %s\n",
|
||||
i, tests[i].expect, tmp);
|
||||
log_info("test #%d: http[%s], ICU[%s], expect %s, %s\n",
|
||||
i, http[tests[i].httpSet], tests[i].icuSet, tests[i].expect, acceptResult(tests[i].res));
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
package com.ibm.icu.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
@ -18,6 +17,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
import com.ibm.icu.impl.Utility;
|
||||
import com.ibm.icu.text.BreakIterator;
|
||||
@ -259,14 +259,10 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
|
||||
if (isFrozen()) {
|
||||
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||||
}
|
||||
ULocale[] acceptLocales = null;
|
||||
try {
|
||||
acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
|
||||
} catch (ParseException pe) {
|
||||
//TODO: revisit after 3.8
|
||||
throw new IllegalArgumentException("Invalid Accept-Language string");
|
||||
}
|
||||
return setLocales(acceptLocales);
|
||||
Set<ULocale> acceptSet = LocalePriorityList.add(acceptLanguageString).build().getULocales();
|
||||
// processLocales() wants a List even though it only iterates front-to-back.
|
||||
locales = processLocales(new ArrayList<>(acceptSet));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -815,6 +811,9 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
protected List<ULocale> processLocales(List<ULocale> inputLocales) {
|
||||
// Note: Some of the callers, and non-ICU call sites, could be simpler/more efficient
|
||||
// if this method took a Collection or even an Iterable.
|
||||
// Maybe we can change it since this is still @draft and probably not widely overridden.
|
||||
List<ULocale> result = new ArrayList<>();
|
||||
/*
|
||||
* Step 1: Relocate later occurrence of more specific locale
|
||||
@ -824,9 +823,7 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
|
||||
* Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA
|
||||
* After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
|
||||
*/
|
||||
for (int i = 0; i < inputLocales.size(); i++) {
|
||||
ULocale uloc = inputLocales.get(i);
|
||||
|
||||
for (ULocale uloc : inputLocales) {
|
||||
String language = uloc.getLanguage();
|
||||
String script = uloc.getScript();
|
||||
String country = uloc.getCountry();
|
||||
|
@ -12,7 +12,6 @@ package com.ibm.icu.util;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -34,7 +33,6 @@ import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.ICUResourceTableAccess;
|
||||
import com.ibm.icu.impl.LocaleIDParser;
|
||||
import com.ibm.icu.impl.LocaleIDs;
|
||||
import com.ibm.icu.impl.LocaleUtility;
|
||||
import com.ibm.icu.impl.SoftCache;
|
||||
import com.ibm.icu.impl.locale.AsciiUtil;
|
||||
import com.ibm.icu.impl.locale.BaseLocale;
|
||||
@ -417,15 +415,6 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ULocale object from a {@link java.util.Locale}.
|
||||
* @param loc a {@link java.util.Locale}
|
||||
*/
|
||||
private ULocale(Locale loc) {
|
||||
this.localeID = getName(forLocale(loc).toString());
|
||||
this.locale = loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@icu} Returns a ULocale object for a {@link java.util.Locale}.
|
||||
* The ULocale is canonicalized.
|
||||
@ -2130,28 +2119,42 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
* ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
|
||||
* availableLocales matched). No ULocale array element should be null; behavior is
|
||||
* undefined if this is the case.
|
||||
*
|
||||
* <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
|
||||
*
|
||||
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
|
||||
* @param availableLocales list of available locales. One of these will be returned.
|
||||
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||||
* the fallback status
|
||||
* @return one of the locales from the availableLocales list, or null if none match
|
||||
* @stable ICU 3.4
|
||||
* @see LocaleMatcher
|
||||
* @see LocalePriorityList
|
||||
*/
|
||||
public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,
|
||||
boolean[] fallback) {
|
||||
if (acceptLanguageList == null) {
|
||||
throw new NullPointerException();
|
||||
if (fallback != null) {
|
||||
fallback[0] = true;
|
||||
}
|
||||
ULocale acceptList[] = null;
|
||||
LocalePriorityList desired;
|
||||
try {
|
||||
acceptList = parseAcceptLanguage(acceptLanguageList, true);
|
||||
} catch (ParseException pe) {
|
||||
acceptList = null;
|
||||
}
|
||||
if (acceptList == null) {
|
||||
desired = LocalePriorityList.add(acceptLanguageList).build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
return acceptLanguage(acceptList, availableLocales, fallback);
|
||||
LocaleMatcher.Builder builder = LocaleMatcher.builder();
|
||||
for (ULocale locale : availableLocales) {
|
||||
builder.addSupportedULocale(locale);
|
||||
}
|
||||
LocaleMatcher matcher = builder.build();
|
||||
LocaleMatcher.Result result = matcher.getBestMatchResult(desired);
|
||||
if (result.getDesiredIndex() >= 0) {
|
||||
if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
|
||||
fallback[0] = false;
|
||||
}
|
||||
return result.getSupportedULocale();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2162,57 +2165,39 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
* will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT
|
||||
* locale was used as a fallback (because nothing else in availableLocales matched).
|
||||
* No ULocale array element should be null; behavior is undefined if this is the case.
|
||||
*
|
||||
* <p>This is a thin wrapper over {@link LocaleMatcher}.
|
||||
*
|
||||
* @param acceptLanguageList list of acceptable locales
|
||||
* @param availableLocales list of available locales. One of these will be returned.
|
||||
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||||
* the fallback status
|
||||
* @return one of the locales from the availableLocales list, or null if none match
|
||||
* @stable ICU 3.4
|
||||
* @see LocaleMatcher
|
||||
*/
|
||||
|
||||
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[]
|
||||
availableLocales, boolean[] fallback) {
|
||||
// fallbacklist
|
||||
int i,j;
|
||||
if(fallback != null) {
|
||||
fallback[0]=true;
|
||||
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales,
|
||||
boolean[] fallback) {
|
||||
if (fallback != null) {
|
||||
fallback[0] = true;
|
||||
}
|
||||
for(i=0;i<acceptLanguageList.length;i++) {
|
||||
ULocale aLocale = acceptLanguageList[i];
|
||||
boolean[] setFallback = fallback;
|
||||
do {
|
||||
for(j=0;j<availableLocales.length;j++) {
|
||||
if(availableLocales[j].equals(aLocale)) {
|
||||
if(setFallback != null) {
|
||||
setFallback[0]=false; // first time with this locale - not a fallback.
|
||||
}
|
||||
return availableLocales[j];
|
||||
}
|
||||
// compare to scriptless alias, so locales such as
|
||||
// zh_TW, zh_CN are considered as available locales - see #7190
|
||||
if (aLocale.getScript().length() == 0
|
||||
&& availableLocales[j].getScript().length() > 0
|
||||
&& availableLocales[j].getLanguage().equals(aLocale.getLanguage())
|
||||
&& availableLocales[j].getCountry().equals(aLocale.getCountry())
|
||||
&& availableLocales[j].getVariant().equals(aLocale.getVariant())) {
|
||||
ULocale minAvail = ULocale.minimizeSubtags(availableLocales[j]);
|
||||
if (minAvail.getScript().length() == 0) {
|
||||
if(setFallback != null) {
|
||||
setFallback[0] = false; // not a fallback.
|
||||
}
|
||||
return aLocale;
|
||||
}
|
||||
}
|
||||
}
|
||||
Locale loc = aLocale.toLocale();
|
||||
Locale parent = LocaleUtility.fallback(loc);
|
||||
if(parent != null) {
|
||||
aLocale = new ULocale(parent);
|
||||
} else {
|
||||
aLocale = null;
|
||||
}
|
||||
setFallback = null; // Do not set fallback in later iterations
|
||||
} while (aLocale != null);
|
||||
LocaleMatcher.Builder builder = LocaleMatcher.builder();
|
||||
for (ULocale locale : availableLocales) {
|
||||
builder.addSupportedULocale(locale);
|
||||
}
|
||||
LocaleMatcher matcher = builder.build();
|
||||
LocaleMatcher.Result result;
|
||||
if (acceptLanguageList.length == 1) {
|
||||
result = matcher.getBestMatchResult(acceptLanguageList[0]);
|
||||
} else {
|
||||
result = matcher.getBestMatchResult(Arrays.asList(acceptLanguageList));
|
||||
}
|
||||
if (result.getDesiredIndex() >= 0) {
|
||||
if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
|
||||
fallback[0] = false;
|
||||
}
|
||||
return result.getSupportedULocale();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -2227,12 +2212,17 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
* availableLocales matched). No ULocale array element should be null; behavior is
|
||||
* undefined if this is the case. This function will choose a locale from the
|
||||
* ULocale.getAvailableLocales() list as available.
|
||||
*
|
||||
* <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
|
||||
*
|
||||
* @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
|
||||
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||||
* the fallback status
|
||||
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if
|
||||
* none match
|
||||
* @stable ICU 3.4
|
||||
* @see LocaleMatcher
|
||||
* @see LocalePriorityList
|
||||
*/
|
||||
public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {
|
||||
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
|
||||
@ -2249,275 +2239,21 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
* availableLocales matched). No ULocale array element should be null; behavior is
|
||||
* undefined if this is the case. This function will choose a locale from the
|
||||
* ULocale.getAvailableLocales() list as available.
|
||||
*
|
||||
* <p>This is a thin wrapper over {@link LocaleMatcher}.
|
||||
*
|
||||
* @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
|
||||
* @param fallback if non-null, a 1-element array containing a boolean to be set with
|
||||
* the fallback status
|
||||
* @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
|
||||
* @stable ICU 3.4
|
||||
* @see LocaleMatcher
|
||||
*/
|
||||
public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) {
|
||||
return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
|
||||
fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package local method used for parsing Accept-Language string
|
||||
*/
|
||||
static ULocale[] parseAcceptLanguage(String acceptLanguage, boolean isLenient)
|
||||
throws ParseException {
|
||||
class ULocaleAcceptLanguageQ implements Comparable<ULocaleAcceptLanguageQ> {
|
||||
private double q;
|
||||
private double serial;
|
||||
public ULocaleAcceptLanguageQ(double theq, int theserial) {
|
||||
q = theq;
|
||||
serial = theserial;
|
||||
}
|
||||
@Override
|
||||
public int compareTo(ULocaleAcceptLanguageQ other) {
|
||||
if (q > other.q) { // reverse - to sort in descending order
|
||||
return -1;
|
||||
} else if (q < other.q) {
|
||||
return 1;
|
||||
}
|
||||
if (serial < other.serial) {
|
||||
return -1;
|
||||
} else if (serial > other.serial) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0; // same object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse out the acceptLanguage into an array
|
||||
TreeMap<ULocaleAcceptLanguageQ, ULocale> map =
|
||||
new TreeMap<>();
|
||||
StringBuilder languageRangeBuf = new StringBuilder();
|
||||
StringBuilder qvalBuf = new StringBuilder();
|
||||
int state = 0;
|
||||
acceptLanguage += ","; // append comma to simplify the parsing code
|
||||
int n;
|
||||
boolean subTag = false;
|
||||
boolean q1 = false;
|
||||
for (n = 0; n < acceptLanguage.length(); n++) {
|
||||
boolean gotLanguageQ = false;
|
||||
char c = acceptLanguage.charAt(n);
|
||||
switch (state) {
|
||||
case 0: // before language-range start
|
||||
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
|
||||
// in language-range
|
||||
languageRangeBuf.append(c);
|
||||
state = 1;
|
||||
subTag = false;
|
||||
} else if (c == '*') {
|
||||
languageRangeBuf.append(c);
|
||||
state = 2;
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid character
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 1: // in language-range
|
||||
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
|
||||
languageRangeBuf.append(c);
|
||||
} else if (c == '-') {
|
||||
subTag = true;
|
||||
languageRangeBuf.append(c);
|
||||
} else if (c == '_') {
|
||||
if (isLenient) {
|
||||
subTag = true;
|
||||
languageRangeBuf.append(c);
|
||||
} else {
|
||||
state = -1;
|
||||
}
|
||||
} else if ('0' <= c && c <= '9') {
|
||||
if (subTag) {
|
||||
languageRangeBuf.append(c);
|
||||
} else {
|
||||
// DIGIT is allowed only in language sub tag
|
||||
state = -1;
|
||||
}
|
||||
} else if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
// language-range end
|
||||
state = 3;
|
||||
} else if (c == ';') {
|
||||
// before q
|
||||
state = 4;
|
||||
} else {
|
||||
// invalid character for language-range
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 2: // saw wild card range
|
||||
if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
// language-range end
|
||||
state = 3;
|
||||
} else if (c == ';') {
|
||||
// before q
|
||||
state = 4;
|
||||
} else {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 3: // language-range end
|
||||
if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c == ';') {
|
||||
// before q
|
||||
state =4;
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 4: // before q
|
||||
if (c == 'q') {
|
||||
// before equal
|
||||
state = 5;
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 5: // before equal
|
||||
if (c == '=') {
|
||||
// before q value
|
||||
state = 6;
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 6: // before q value
|
||||
if (c == '0') {
|
||||
// q value start with 0
|
||||
q1 = false;
|
||||
qvalBuf.append(c);
|
||||
state = 7;
|
||||
} else if (c == '1') {
|
||||
// q value start with 1
|
||||
qvalBuf.append(c);
|
||||
state = 7;
|
||||
} else if (c == '.') {
|
||||
if (isLenient) {
|
||||
qvalBuf.append(c);
|
||||
state = 8;
|
||||
} else {
|
||||
state = -1;
|
||||
}
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 7: // q value start
|
||||
if (c == '.') {
|
||||
// before q value fraction part
|
||||
qvalBuf.append(c);
|
||||
state = 8;
|
||||
} else if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
// after q value
|
||||
state = 10;
|
||||
} else {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 8: // before q value fraction part
|
||||
if ('0' <= c && c <= '9') {
|
||||
if (q1 && c != '0' && !isLenient) {
|
||||
// if q value starts with 1, the fraction part must be 0
|
||||
state = -1;
|
||||
} else {
|
||||
// in q value fraction part
|
||||
qvalBuf.append(c);
|
||||
state = 9;
|
||||
}
|
||||
} else {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 9: // in q value fraction part
|
||||
if ('0' <= c && c <= '9') {
|
||||
if (q1 && c != '0') {
|
||||
// if q value starts with 1, the fraction part must be 0
|
||||
state = -1;
|
||||
} else {
|
||||
qvalBuf.append(c);
|
||||
}
|
||||
} else if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c == ' ' || c == '\t') {
|
||||
// after q value
|
||||
state = 10;
|
||||
} else {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
case 10: // after q value
|
||||
if (c == ',') {
|
||||
// language-q end
|
||||
gotLanguageQ = true;
|
||||
} else if (c != ' ' && c != '\t') {
|
||||
// invalid
|
||||
state = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state == -1) {
|
||||
// error state
|
||||
throw new ParseException("Invalid Accept-Language", n);
|
||||
}
|
||||
if (gotLanguageQ) {
|
||||
double q = 1.0;
|
||||
if (qvalBuf.length() != 0) {
|
||||
try {
|
||||
q = Double.parseDouble(qvalBuf.toString());
|
||||
} catch (NumberFormatException nfe) {
|
||||
// Already validated, so it should never happen
|
||||
q = 1.0;
|
||||
}
|
||||
if (q > 1.0) {
|
||||
q = 1.0;
|
||||
}
|
||||
}
|
||||
if (languageRangeBuf.charAt(0) != '*') {
|
||||
int serial = map.size();
|
||||
ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q, serial);
|
||||
// sort in reverse order.. 1.0, 0.9, 0.8 .. etc
|
||||
map.put(entry, new ULocale(canonicalize(languageRangeBuf.toString())));
|
||||
}
|
||||
|
||||
// reset buffer and parse state
|
||||
languageRangeBuf.setLength(0);
|
||||
qvalBuf.setLength(0);
|
||||
state = 0;
|
||||
}
|
||||
}
|
||||
if (state != 0) {
|
||||
// Well, the parser should handle all cases. So just in case.
|
||||
throw new ParseException("Invalid AcceptlLanguage", n);
|
||||
}
|
||||
|
||||
// pull out the map
|
||||
ULocale acceptList[] = map.values().toArray(new ULocale[map.size()]);
|
||||
return acceptList;
|
||||
}
|
||||
|
||||
private static final String UNDEFINED_LANGUAGE = "und";
|
||||
private static final String UNDEFINED_SCRIPT = "Zzzz";
|
||||
private static final String UNDEFINED_REGION = "ZZ";
|
||||
@ -3396,7 +3132,7 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
|
||||
|
||||
List<String>subtags = tag.getVariants();
|
||||
// ICU-20478: Sort variants per UTS35.
|
||||
ArrayList<String> variants = new ArrayList<String>(subtags);
|
||||
ArrayList<String> variants = new ArrayList<>(subtags);
|
||||
Collections.sort(variants);
|
||||
for (String s : variants) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
|
@ -239,7 +239,7 @@ public class GlobalizationPreferencesTest extends TestFmwk {
|
||||
{"fr_CA", "fr"},
|
||||
{"fr", "fr_CA"},
|
||||
{"es", "fr", "en_US"},
|
||||
{"zh_Hans", "zh_Hans_CN"},
|
||||
{"zh_CN", "zh_Hans", "zh_Hans_CN"},
|
||||
{"en_US_123"},
|
||||
{"es_US", "es"},
|
||||
{"de_DE", "es", "fr_FR"},
|
||||
@ -261,7 +261,7 @@ public class GlobalizationPreferencesTest extends TestFmwk {
|
||||
{"fr_CA", "fr"},
|
||||
{"fr_CA", "fr"},
|
||||
{"es", "fr", "en_US", "en"},
|
||||
{"zh_Hans_CN", "zh_Hans", "zh"},
|
||||
{"zh_Hans_CN", "zh_CN", "zh_Hans", "zh"},
|
||||
{"en_US_123", "en_US", "en"},
|
||||
{"es_US", "es"},
|
||||
{"de_DE", "de", "es", "fr_FR", "fr"},
|
||||
@ -348,6 +348,11 @@ public class GlobalizationPreferencesTest extends TestFmwk {
|
||||
gp.setLocales(acceptLanguage);
|
||||
|
||||
List<ULocale> resultLocales = gp.getLocales();
|
||||
List<ULocale> expectedLocales = new ArrayList<>(RESULTS_LOCALEIDS[i].length);
|
||||
for (String exp : RESULTS_LOCALEIDS[i]) {
|
||||
expectedLocales.add(new ULocale(exp));
|
||||
}
|
||||
assertEquals("#" + i, expectedLocales.toString(), resultLocales.toString());
|
||||
if (resultLocales.size() != RESULTS_LOCALEIDS[i].length) {
|
||||
StringBuilder res = new StringBuilder();
|
||||
for (ULocale l : resultLocales) {
|
||||
@ -377,22 +382,23 @@ public class GlobalizationPreferencesTest extends TestFmwk {
|
||||
}
|
||||
|
||||
// Invalid accept-language
|
||||
logln("Set locale - ko_KR");
|
||||
gp.setLocale(new ULocale("ko_KR"));
|
||||
boolean bException = false;
|
||||
try {
|
||||
logln("Set invlaid accept-language - ko=100");
|
||||
gp.setLocales("ko=100");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logln("IllegalArgumentException was thrown");
|
||||
bException = true;
|
||||
}
|
||||
if (!bException) {
|
||||
errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100");
|
||||
}
|
||||
if (!gp.getLocale(0).toString().equals("ko_KR")) {
|
||||
errln("FAIL: Previous valid locale list had gone");
|
||||
}
|
||||
// ICU-20700 changed the parser to using LocalePriorityList which is more lenient.
|
||||
// logln("Set locale - ko_KR");
|
||||
// gp.setLocale(new ULocale("ko_KR"));
|
||||
// boolean bException = false;
|
||||
// try {
|
||||
// logln("Set invlaid accept-language - ko=100");
|
||||
// gp.setLocales("ko=100");
|
||||
// } catch (IllegalArgumentException iae) {
|
||||
// logln("IllegalArgumentException was thrown");
|
||||
// bException = true;
|
||||
// }
|
||||
// if (!bException) {
|
||||
// errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100");
|
||||
// }
|
||||
// if (!gp.getLocale(0).toString().equals("ko_KR")) {
|
||||
// errln("FAIL: Previous valid locale list had gone");
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -22,7 +22,6 @@ import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -34,7 +33,6 @@ import org.junit.runners.JUnit4;
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.dev.test.TestUtil;
|
||||
import com.ibm.icu.dev.test.TestUtil.JavaVendor;
|
||||
import com.ibm.icu.lang.UCharacter;
|
||||
import com.ibm.icu.text.DateFormat;
|
||||
import com.ibm.icu.text.DecimalFormat;
|
||||
import com.ibm.icu.text.DisplayContext;
|
||||
@ -46,6 +44,7 @@ import com.ibm.icu.text.SimpleDateFormat;
|
||||
import com.ibm.icu.util.Calendar;
|
||||
import com.ibm.icu.util.IllformedLocaleException;
|
||||
import com.ibm.icu.util.LocaleData;
|
||||
import com.ibm.icu.util.LocalePriorityList;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.ULocale.Builder;
|
||||
import com.ibm.icu.util.ULocale.Category;
|
||||
@ -1590,15 +1589,15 @@ public class ULocaleTest extends TestFmwk {
|
||||
/*3*/ { null, "true" },
|
||||
/*4*/ { "es", "false" },
|
||||
/*5*/ { "de", "false" },
|
||||
/*6*/ { "zh_Hant_TW", "false" },
|
||||
/*7*/ { "zh", "true" },
|
||||
/*6*/ { "zh_Hant_TW", "true" },
|
||||
/*7*/ { "zh_Hant", "true" },
|
||||
};
|
||||
|
||||
private static final String ACCEPT_LANGUAGE_HTTP[] = {
|
||||
/*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=.01",
|
||||
/*1*/ "ja;q=0.5, en;q=0.8, tlh",
|
||||
/*2*/ "en-zzz, de-lx;q=0.8",
|
||||
/*3*/ "mga-ie;q=0.9, tlh",
|
||||
/*3*/ "mga-ie;q=0.9, sux",
|
||||
/*4*/ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
|
||||
@ -1610,16 +1609,16 @@ public class ULocaleTest extends TestFmwk {
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
|
||||
"xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+
|
||||
"es",
|
||||
/*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8",
|
||||
/*6*/ "zh-tw",
|
||||
/*7*/ "zh-hant-cn",
|
||||
/*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8",
|
||||
/*6*/ "zh-tw",
|
||||
/*7*/ "zh-hant-cn",
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
public void TestAcceptLanguage() {
|
||||
for(int i = 0 ; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
|
||||
Boolean expectBoolean = new Boolean(ACCEPT_LANGUAGE_TESTS[i][1]);
|
||||
Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
|
||||
String expectLocale=ACCEPT_LANGUAGE_TESTS[i][0];
|
||||
|
||||
logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");
|
||||
@ -1627,128 +1626,50 @@ public class ULocaleTest extends TestFmwk {
|
||||
boolean r[] = { false };
|
||||
ULocale n = ULocale.acceptLanguage(ACCEPT_LANGUAGE_HTTP[i], r);
|
||||
if((n==null)&&(expectLocale!=null)) {
|
||||
errln("result was null! line #" + i);
|
||||
errln("#" + i + ": result was null!");
|
||||
continue;
|
||||
}
|
||||
if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) {
|
||||
logln(" locale: OK." );
|
||||
logln("#" + i + ": locale: OK." );
|
||||
} else {
|
||||
errln("expected " + expectLocale + " but got " + n.toString());
|
||||
errln("#" + i + ": locale: expected " + expectLocale + " but got " + n);
|
||||
}
|
||||
if(expectBoolean.equals(new Boolean(r[0]))) {
|
||||
logln(" bool: OK.");
|
||||
Boolean actualBoolean = Boolean.valueOf(r[0]);
|
||||
if(expectBoolean.equals(actualBoolean)) {
|
||||
logln("#" + i + ": fallback: OK.");
|
||||
} else {
|
||||
errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString());
|
||||
errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ULocale[] StringToULocaleArray(String acceptLanguageList){
|
||||
//following code is copied from
|
||||
//ULocale.acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, boolean[] fallback)
|
||||
class ULocaleAcceptLanguageQ implements Comparable {
|
||||
private double q;
|
||||
private double serial;
|
||||
public ULocaleAcceptLanguageQ(double theq, int theserial) {
|
||||
q = theq;
|
||||
serial = theserial;
|
||||
}
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o;
|
||||
if(q > other.q) { // reverse - to sort in descending order
|
||||
return -1;
|
||||
} else if(q < other.q) {
|
||||
return 1;
|
||||
}
|
||||
if(serial < other.serial) {
|
||||
return -1;
|
||||
} else if(serial > other.serial) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0; // same object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1st: parse out the acceptLanguageList into an array
|
||||
|
||||
TreeMap map = new TreeMap();
|
||||
|
||||
final int l = acceptLanguageList.length();
|
||||
int n;
|
||||
for(n=0;n<l;n++) {
|
||||
int itemEnd = acceptLanguageList.indexOf(',',n);
|
||||
if(itemEnd == -1) {
|
||||
itemEnd = l;
|
||||
}
|
||||
int paramEnd = acceptLanguageList.indexOf(';',n);
|
||||
double q = 1.0;
|
||||
|
||||
if((paramEnd != -1) && (paramEnd < itemEnd)) {
|
||||
/* semicolon (;) is closer than end (,) */
|
||||
int t = paramEnd + 1;
|
||||
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
|
||||
t++;
|
||||
}
|
||||
if(acceptLanguageList.charAt(t)=='q') {
|
||||
t++;
|
||||
}
|
||||
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
|
||||
t++;
|
||||
}
|
||||
if(acceptLanguageList.charAt(t)=='=') {
|
||||
t++;
|
||||
}
|
||||
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
|
||||
t++;
|
||||
}
|
||||
try {
|
||||
String val = acceptLanguageList.substring(t,itemEnd).trim();
|
||||
q = Double.parseDouble(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
q = 1.0;
|
||||
}
|
||||
} else {
|
||||
q = 1.0; //default
|
||||
paramEnd = itemEnd;
|
||||
}
|
||||
|
||||
String loc = acceptLanguageList.substring(n,paramEnd).trim();
|
||||
int serial = map.size();
|
||||
ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q,serial);
|
||||
map.put(entry, new ULocale(ULocale.canonicalize(loc))); // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
|
||||
n = itemEnd; // get next item. (n++ will skip over delimiter)
|
||||
}
|
||||
|
||||
// 2. pull out the map
|
||||
ULocale acceptList[] = (ULocale[])map.values().toArray(new ULocale[map.size()]);
|
||||
return acceptList;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestAcceptLanguage2() {
|
||||
for(int i = 0 ; i < (ACCEPT_LANGUAGE_HTTP.length); i++) {
|
||||
Boolean expectBoolean = new Boolean(ACCEPT_LANGUAGE_TESTS[i][1]);
|
||||
Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]);
|
||||
String expectLocale=ACCEPT_LANGUAGE_TESTS[i][0];
|
||||
|
||||
logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")");
|
||||
|
||||
boolean r[] = { false };
|
||||
ULocale n = ULocale.acceptLanguage(StringToULocaleArray(ACCEPT_LANGUAGE_HTTP[i]), r);
|
||||
Set<ULocale> desiredSet =
|
||||
LocalePriorityList.add(ACCEPT_LANGUAGE_HTTP[i]).build().getULocales();
|
||||
ULocale[] desiredArray = desiredSet.toArray(new ULocale[desiredSet.size()]);
|
||||
ULocale n = ULocale.acceptLanguage(desiredArray, r);
|
||||
if((n==null)&&(expectLocale!=null)) {
|
||||
errln("result was null! line #" + i);
|
||||
errln("#" + i + ": result was null!");
|
||||
continue;
|
||||
}
|
||||
if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) {
|
||||
logln(" locale: OK." );
|
||||
logln("#" + i + ": locale: OK.");
|
||||
} else {
|
||||
errln("expected " + expectLocale + " but got " + n.toString());
|
||||
errln("#" + i + ": expected " + expectLocale + " but got " + n.toString());
|
||||
}
|
||||
if(expectBoolean.equals(new Boolean(r[0]))) {
|
||||
logln(" bool: OK.");
|
||||
Boolean actualBoolean = Boolean.valueOf(r[0]);
|
||||
if(expectBoolean.equals(actualBoolean)) {
|
||||
logln("#" + i + ": fallback: OK.");
|
||||
} else {
|
||||
errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString());
|
||||
errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user