2844 lines
85 KiB
C++
2844 lines
85 KiB
C++
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
**********************************************************************
|
|
* Copyright (C) 2009-2015, International Business Machines
|
|
* Corporation and others. All Rights Reserved.
|
|
**********************************************************************
|
|
*/
|
|
|
|
#include "unicode/bytestream.h"
|
|
#include "unicode/utypes.h"
|
|
#include "unicode/ures.h"
|
|
#include "unicode/localpointer.h"
|
|
#include "unicode/putil.h"
|
|
#include "unicode/uenum.h"
|
|
#include "unicode/uloc.h"
|
|
#include "ustr_imp.h"
|
|
#include "bytesinkutil.h"
|
|
#include "charstr.h"
|
|
#include "cmemory.h"
|
|
#include "cstring.h"
|
|
#include "putilimp.h"
|
|
#include "uinvchar.h"
|
|
#include "ulocimp.h"
|
|
#include "uassert.h"
|
|
|
|
|
|
/* struct holding a single variant */
|
|
typedef struct VariantListEntry {
|
|
const char *variant;
|
|
struct VariantListEntry *next;
|
|
} VariantListEntry;
|
|
|
|
/* struct holding a single attribute value */
|
|
struct AttributeListEntry : public icu::UMemory {
|
|
const char *attribute;
|
|
struct AttributeListEntry *next;
|
|
};
|
|
|
|
/* struct holding a single extension */
|
|
struct ExtensionListEntry : public icu::UMemory {
|
|
const char *key;
|
|
const char *value;
|
|
struct ExtensionListEntry *next;
|
|
};
|
|
|
|
#define MAXEXTLANG 3
|
|
typedef struct ULanguageTag {
|
|
char *buf; /* holding parsed subtags */
|
|
const char *language;
|
|
const char *extlang[MAXEXTLANG];
|
|
const char *script;
|
|
const char *region;
|
|
VariantListEntry *variants;
|
|
ExtensionListEntry *extensions;
|
|
const char *privateuse;
|
|
const char *legacy;
|
|
} ULanguageTag;
|
|
|
|
#define MINLEN 2
|
|
#define SEP '-'
|
|
#define PRIVATEUSE 'x'
|
|
#define LDMLEXT 'u'
|
|
|
|
#define LOCALE_SEP '_'
|
|
#define LOCALE_EXT_SEP '@'
|
|
#define LOCALE_KEYWORD_SEP ';'
|
|
#define LOCALE_KEY_TYPE_SEP '='
|
|
|
|
#define ISALPHA(c) uprv_isASCIILetter(c)
|
|
#define ISNUMERIC(c) ((c)>='0' && (c)<='9')
|
|
|
|
static const char EMPTY[] = "";
|
|
static const char LANG_UND[] = "und";
|
|
static const char PRIVATEUSE_KEY[] = "x";
|
|
static const char _POSIX[] = "_POSIX";
|
|
static const char POSIX_KEY[] = "va";
|
|
static const char POSIX_VALUE[] = "posix";
|
|
static const char LOCALE_ATTRIBUTE_KEY[] = "attribute";
|
|
static const char PRIVUSE_VARIANT_PREFIX[] = "lvariant";
|
|
static const char LOCALE_TYPE_YES[] = "yes";
|
|
|
|
#define LANG_UND_LEN 3
|
|
|
|
/*
|
|
Updated on 2018-09-12 from
|
|
https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry .
|
|
|
|
This table has 2 parts. The part for
|
|
legacy language tags (marked as “Type: grandfathered” in BCP 47)
|
|
is generated by the following scripts from the IANA language tag registry.
|
|
|
|
curl https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry |\
|
|
egrep -A 7 'Type: grandfathered' | \
|
|
egrep 'Tag|Prefe' | grep -B1 'Preferred' | grep -v '^--' | \
|
|
awk -n '/Tag/ {printf(" \"%s\", ", $2);} /Preferred/ {printf("\"%s\",\n", $2);}' |\
|
|
tr 'A-Z' 'a-z'
|
|
|
|
|
|
The 2nd part is made of five ICU-specific entries. They're kept for
|
|
the backward compatibility for now, even though there are no preferred
|
|
values. They may have to be removed for the strict BCP 47 compliance.
|
|
|
|
*/
|
|
static const char* const LEGACY[] = {
|
|
/* legacy preferred */
|
|
"art-lojban", "jbo",
|
|
"en-gb-oed", "en-gb-oxendict",
|
|
"i-ami", "ami",
|
|
"i-bnn", "bnn",
|
|
"i-hak", "hak",
|
|
"i-klingon", "tlh",
|
|
"i-lux", "lb",
|
|
"i-navajo", "nv",
|
|
"i-pwn", "pwn",
|
|
"i-tao", "tao",
|
|
"i-tay", "tay",
|
|
"i-tsu", "tsu",
|
|
"no-bok", "nb",
|
|
"no-nyn", "nn",
|
|
"sgn-be-fr", "sfb",
|
|
"sgn-be-nl", "vgt",
|
|
"sgn-ch-de", "sgg",
|
|
"zh-guoyu", "cmn",
|
|
"zh-hakka", "hak",
|
|
"zh-min-nan", "nan",
|
|
"zh-xiang", "hsn",
|
|
|
|
// Legacy tags with no preferred value in the IANA
|
|
// registry. Kept for now for the backward compatibility
|
|
// because ICU has mapped them this way.
|
|
"cel-gaulish", "xtg-x-cel-gaulish",
|
|
"i-default", "en-x-i-default",
|
|
"i-enochian", "und-x-i-enochian",
|
|
"i-mingo", "see-x-i-mingo",
|
|
"zh-min", "nan-x-zh-min",
|
|
};
|
|
|
|
/*
|
|
Updated on 2018-09-12 from
|
|
https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry .
|
|
|
|
The table lists redundant tags with preferred value in the IANA languate tag registry.
|
|
It's generated with the following command:
|
|
|
|
curl https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry |\
|
|
grep 'Type: redundant' -A 5 | egrep '^(Tag:|Prefer)' | grep -B1 'Preferred' | \
|
|
awk -n '/Tag/ {printf(" \"%s\", ", $2);} /Preferred/ {printf("\"%s\",\n", $2);}' | \
|
|
tr 'A-Z' 'a-z'
|
|
|
|
In addition, ja-latn-hepburn-heploc is mapped to ja-latn-alalc97 because
|
|
a variant tag 'hepburn-heploc' has the preferred subtag, 'alaic97'.
|
|
*/
|
|
|
|
static const char* const REDUNDANT[] = {
|
|
// redundant preferred
|
|
"sgn-br", "bzs",
|
|
"sgn-co", "csn",
|
|
"sgn-de", "gsg",
|
|
"sgn-dk", "dsl",
|
|
"sgn-es", "ssp",
|
|
"sgn-fr", "fsl",
|
|
"sgn-gb", "bfi",
|
|
"sgn-gr", "gss",
|
|
"sgn-ie", "isg",
|
|
"sgn-it", "ise",
|
|
"sgn-jp", "jsl",
|
|
"sgn-mx", "mfs",
|
|
"sgn-ni", "ncs",
|
|
"sgn-nl", "dse",
|
|
"sgn-no", "nsl",
|
|
"sgn-pt", "psr",
|
|
"sgn-se", "swl",
|
|
"sgn-us", "ase",
|
|
"sgn-za", "sfs",
|
|
"zh-cmn", "cmn",
|
|
"zh-cmn-hans", "cmn-hans",
|
|
"zh-cmn-hant", "cmn-hant",
|
|
"zh-gan", "gan",
|
|
"zh-wuu", "wuu",
|
|
"zh-yue", "yue",
|
|
|
|
// variant tag with preferred value
|
|
"ja-latn-hepburn-heploc", "ja-latn-alalc97",
|
|
};
|
|
|
|
/*
|
|
Updated on 2018-09-12 from
|
|
https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry .
|
|
|
|
grep 'Type: language' -A 7 language-subtag-registry | egrep 'Subtag|Prefe' | \
|
|
grep -B1 'Preferred' | grep -v '^--' | \
|
|
awk -n '/Subtag/ {printf(" \"%s\", ", $2);} /Preferred/ {printf("\"%s\",\n", $2);}'
|
|
|
|
Make sure that 2-letter language subtags come before 3-letter subtags.
|
|
*/
|
|
static const char DEPRECATEDLANGS[][4] = {
|
|
/* deprecated new */
|
|
"in", "id",
|
|
"iw", "he",
|
|
"ji", "yi",
|
|
"jw", "jv",
|
|
"mo", "ro",
|
|
"aam", "aas",
|
|
"adp", "dz",
|
|
"aue", "ktz",
|
|
"ayx", "nun",
|
|
"bgm", "bcg",
|
|
"bjd", "drl",
|
|
"ccq", "rki",
|
|
"cjr", "mom",
|
|
"cka", "cmr",
|
|
"cmk", "xch",
|
|
"coy", "pij",
|
|
"cqu", "quh",
|
|
"drh", "khk",
|
|
"drw", "prs",
|
|
"gav", "dev",
|
|
"gfx", "vaj",
|
|
"ggn", "gvr",
|
|
"gti", "nyc",
|
|
"guv", "duz",
|
|
"hrr", "jal",
|
|
"ibi", "opa",
|
|
"ilw", "gal",
|
|
"jeg", "oyb",
|
|
"kgc", "tdf",
|
|
"kgh", "kml",
|
|
"koj", "kwv",
|
|
"krm", "bmf",
|
|
"ktr", "dtp",
|
|
"kvs", "gdj",
|
|
"kwq", "yam",
|
|
"kxe", "tvd",
|
|
"kzj", "dtp",
|
|
"kzt", "dtp",
|
|
"lii", "raq",
|
|
"lmm", "rmx",
|
|
"meg", "cir",
|
|
"mst", "mry",
|
|
"mwj", "vaj",
|
|
"myt", "mry",
|
|
"nad", "xny",
|
|
"ncp", "kdz",
|
|
"nnx", "ngv",
|
|
"nts", "pij",
|
|
"oun", "vaj",
|
|
"pcr", "adx",
|
|
"pmc", "huw",
|
|
"pmu", "phr",
|
|
"ppa", "bfy",
|
|
"ppr", "lcq",
|
|
"pry", "prt",
|
|
"puz", "pub",
|
|
"sca", "hle",
|
|
"skk", "oyb",
|
|
"tdu", "dtp",
|
|
"thc", "tpo",
|
|
"thx", "oyb",
|
|
"tie", "ras",
|
|
"tkk", "twm",
|
|
"tlw", "weo",
|
|
"tmp", "tyj",
|
|
"tne", "kak",
|
|
"tnf", "prs",
|
|
"tsf", "taj",
|
|
"uok", "ema",
|
|
"xba", "cax",
|
|
"xia", "acn",
|
|
"xkh", "waw",
|
|
"xsj", "suj",
|
|
"ybd", "rki",
|
|
"yma", "lrr",
|
|
"ymt", "mtm",
|
|
"yos", "zom",
|
|
"yuu", "yug",
|
|
};
|
|
|
|
/*
|
|
Updated on 2018-04-24 from
|
|
|
|
curl https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry | \
|
|
grep 'Type: region' -A 7 | egrep 'Subtag|Prefe' | \
|
|
grep -B1 'Preferred' | \
|
|
awk -n '/Subtag/ {printf(" \"%s\", ", $2);} /Preferred/ {printf("\"%s\",\n", $2);}'
|
|
*/
|
|
static const char DEPRECATEDREGIONS[][3] = {
|
|
/* deprecated new */
|
|
"BU", "MM",
|
|
"DD", "DE",
|
|
"FX", "FR",
|
|
"TP", "TL",
|
|
"YD", "YE",
|
|
"ZR", "CD",
|
|
};
|
|
|
|
/*
|
|
* -------------------------------------------------
|
|
*
|
|
* These ultag_ functions may be exposed as APIs later
|
|
*
|
|
* -------------------------------------------------
|
|
*/
|
|
|
|
static ULanguageTag*
|
|
ultag_parse(const char* tag, int32_t tagLen, int32_t* parsedLen, UErrorCode* status);
|
|
|
|
static void
|
|
ultag_close(ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getLanguage(const ULanguageTag* langtag);
|
|
|
|
#if 0
|
|
static const char*
|
|
ultag_getJDKLanguage(const ULanguageTag* langtag);
|
|
#endif
|
|
|
|
static const char*
|
|
ultag_getExtlang(const ULanguageTag* langtag, int32_t idx);
|
|
|
|
static int32_t
|
|
ultag_getExtlangSize(const ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getScript(const ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getRegion(const ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getVariant(const ULanguageTag* langtag, int32_t idx);
|
|
|
|
static int32_t
|
|
ultag_getVariantsSize(const ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getExtensionKey(const ULanguageTag* langtag, int32_t idx);
|
|
|
|
static const char*
|
|
ultag_getExtensionValue(const ULanguageTag* langtag, int32_t idx);
|
|
|
|
static int32_t
|
|
ultag_getExtensionsSize(const ULanguageTag* langtag);
|
|
|
|
static const char*
|
|
ultag_getPrivateUse(const ULanguageTag* langtag);
|
|
|
|
#if 0
|
|
static const char*
|
|
ultag_getLegacy(const ULanguageTag* langtag);
|
|
#endif
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* \class LocalULanguageTagPointer
|
|
* "Smart pointer" class, closes a ULanguageTag via ultag_close().
|
|
* For most methods see the LocalPointerBase base class.
|
|
*
|
|
* @see LocalPointerBase
|
|
* @see LocalPointer
|
|
* @internal
|
|
*/
|
|
U_DEFINE_LOCAL_OPEN_POINTER(LocalULanguageTagPointer, ULanguageTag, ultag_close);
|
|
|
|
U_NAMESPACE_END
|
|
|
|
/*
|
|
* -------------------------------------------------
|
|
*
|
|
* Language subtag syntax validation functions
|
|
*
|
|
* -------------------------------------------------
|
|
*/
|
|
|
|
static UBool
|
|
_isAlphaString(const char* s, int32_t len) {
|
|
int32_t i;
|
|
for (i = 0; i < len; i++) {
|
|
if (!ISALPHA(*(s + i))) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static UBool
|
|
_isNumericString(const char* s, int32_t len) {
|
|
int32_t i;
|
|
for (i = 0; i < len; i++) {
|
|
if (!ISNUMERIC(*(s + i))) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static UBool
|
|
_isAlphaNumericString(const char* s, int32_t len) {
|
|
int32_t i;
|
|
for (i = 0; i < len; i++) {
|
|
if (!ISALPHA(*(s + i)) && !ISNUMERIC(*(s + i))) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static UBool
|
|
_isAlphaNumericStringLimitedLength(const char* s, int32_t len, int32_t min, int32_t max) {
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len >= min && len <= max && _isAlphaNumericString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isLanguageSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* unicode_language_subtag = alpha{2,3} | alpha{5,8};
|
|
* NOTE: Per ICUTC 2019/01/23- accepting alpha 4
|
|
* See ICU-20372
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len >= 2 && len <= 8 && _isAlphaString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isExtlangSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* extlang = 3ALPHA ; selected ISO 639 codes
|
|
* *2("-" 3ALPHA) ; permanently reserved
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 3 && _isAlphaString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isScriptSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* script = 4ALPHA ; ISO 15924 code
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 4 && _isAlphaString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isRegionSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* region = 2ALPHA ; ISO 3166-1 code
|
|
* / 3DIGIT ; UN M.49 code
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 2 && _isAlphaString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
if (len == 3 && _isNumericString(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isVariantSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* variant = 5*8alphanum ; registered variants
|
|
* / (DIGIT 3alphanum)
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (_isAlphaNumericStringLimitedLength(s, len, 5, 8)) {
|
|
return TRUE;
|
|
}
|
|
if (len == 4 && ISNUMERIC(*s) && _isAlphaNumericString(s + 1, 3)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isSepListOf(UBool (*test)(const char*, int32_t), const char* s, int32_t len) {
|
|
const char *p = s;
|
|
const char *pSubtag = NULL;
|
|
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
|
|
while ((p - s) < len) {
|
|
if (*p == SEP) {
|
|
if (pSubtag == NULL) {
|
|
return FALSE;
|
|
}
|
|
if (!test(pSubtag, (int32_t)(p - pSubtag))) {
|
|
return FALSE;
|
|
}
|
|
pSubtag = NULL;
|
|
} else if (pSubtag == NULL) {
|
|
pSubtag = p;
|
|
}
|
|
p++;
|
|
}
|
|
if (pSubtag == NULL) {
|
|
return FALSE;
|
|
}
|
|
return test(pSubtag, (int32_t)(p - pSubtag));
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isVariantSubtags(const char* s, int32_t len) {
|
|
return _isSepListOf(&_isVariantSubtag, s, len);
|
|
}
|
|
|
|
// This is for the ICU-specific "lvariant" handling.
|
|
static UBool
|
|
_isPrivateuseVariantSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* variant = 1*8alphanum ; registered variants
|
|
* / (DIGIT 3alphanum)
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len , 1, 8);
|
|
}
|
|
|
|
static UBool
|
|
_isExtensionSingleton(const char* s, int32_t len) {
|
|
/*
|
|
* extension = singleton 1*("-" (2*8alphanum))
|
|
*
|
|
* singleton = DIGIT ; 0 - 9
|
|
* / %x41-57 ; A - W
|
|
* / %x59-5A ; Y - Z
|
|
* / %x61-77 ; a - w
|
|
* / %x79-7A ; y - z
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 1 && (ISALPHA(*s) || ISNUMERIC(*s)) && (uprv_tolower(*s) != PRIVATEUSE)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isExtensionSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* extension = singleton 1*("-" (2*8alphanum))
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len, 2, 8);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isExtensionSubtags(const char* s, int32_t len) {
|
|
return _isSepListOf(&_isExtensionSubtag, s, len);
|
|
}
|
|
|
|
static UBool
|
|
_isPrivateuseValueSubtag(const char* s, int32_t len) {
|
|
/*
|
|
* privateuse = "x" 1*("-" (1*8alphanum))
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len, 1, 8);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isPrivateuseValueSubtags(const char* s, int32_t len) {
|
|
return _isSepListOf(&_isPrivateuseValueSubtag, s, len);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isUnicodeLocaleAttribute(const char* s, int32_t len) {
|
|
/*
|
|
* attribute = alphanum{3,8} ;
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len , 3, 8);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isUnicodeLocaleAttributes(const char* s, int32_t len) {
|
|
return _isSepListOf(&ultag_isUnicodeLocaleAttribute, s, len);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isUnicodeLocaleKey(const char* s, int32_t len) {
|
|
/*
|
|
* key = alphanum alpha ;
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 2 && (ISALPHA(*s) || ISNUMERIC(*s)) && ISALPHA(s[1])) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
_isUnicodeLocaleTypeSubtag(const char*s, int32_t len) {
|
|
/*
|
|
* alphanum{3,8}
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len , 3, 8);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isUnicodeLocaleType(const char*s, int32_t len) {
|
|
/*
|
|
* type = alphanum{3,8} (sep alphanum{3,8})* ;
|
|
*/
|
|
return _isSepListOf(&_isUnicodeLocaleTypeSubtag, s, len);
|
|
}
|
|
|
|
static UBool
|
|
_isTKey(const char* s, int32_t len)
|
|
{
|
|
/*
|
|
* tkey = alpha digit ;
|
|
*/
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
if (len == 2 && ISALPHA(*s) && ISNUMERIC(*(s + 1))) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isTValue(const char* s, int32_t len)
|
|
{
|
|
/*
|
|
* tvalue = (sep alphanum{3,8})+ ;
|
|
*/
|
|
return _isAlphaNumericStringLimitedLength(s, len , 3, 8);
|
|
}
|
|
|
|
static UBool
|
|
_isTransformedExtensionSubtag(int32_t& state, const char* s, int32_t len)
|
|
{
|
|
const int32_t kStart = 0; // Start, wait for unicode_language_subtag, tkey or end
|
|
const int32_t kGotLanguage = 1; // Got unicode_language_subtag, wait for unicode_script_subtag,
|
|
// unicode_region_subtag, unicode_variant_subtag, tkey or end
|
|
const int32_t kGotScript = 2; // Got unicode_script_subtag, wait for unicode_region_subtag,
|
|
// unicode_variant_subtag, tkey, or end
|
|
const int32_t kGotRegion = 3; // Got unicode_region_subtag, wait for unicode_variant_subtag,
|
|
// tkey, or end.
|
|
const int32_t kGotVariant = 4; // Got unicode_variant_subtag, wait for unicode_variant_subtag
|
|
// tkey or end.
|
|
const int32_t kGotTKey = -1; // Got tkey, wait for tvalue. ERROR if stop here.
|
|
const int32_t kGotTValue = 6; // Got tvalue, wait for tkey, tvalue or end
|
|
|
|
switch (state) {
|
|
case kStart:
|
|
if (ultag_isLanguageSubtag(s, len)) {
|
|
state = kGotLanguage;
|
|
return TRUE;
|
|
}
|
|
if (_isTKey(s, len)) {
|
|
state = kGotTKey;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
case kGotLanguage:
|
|
if (ultag_isScriptSubtag(s, len)) {
|
|
state = kGotScript;
|
|
return TRUE;
|
|
}
|
|
U_FALLTHROUGH;
|
|
case kGotScript:
|
|
if (ultag_isRegionSubtag(s, len)) {
|
|
state = kGotRegion;
|
|
return TRUE;
|
|
}
|
|
U_FALLTHROUGH;
|
|
case kGotRegion:
|
|
U_FALLTHROUGH;
|
|
case kGotVariant:
|
|
if (_isVariantSubtag(s, len)) {
|
|
state = kGotVariant;
|
|
return TRUE;
|
|
}
|
|
if (_isTKey(s, len)) {
|
|
state = kGotTKey;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
case kGotTKey:
|
|
if (_isTValue(s, len)) {
|
|
state = kGotTValue;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
case kGotTValue:
|
|
if (_isTKey(s, len)) {
|
|
state = kGotTKey;
|
|
return TRUE;
|
|
}
|
|
if (_isTValue(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isUnicodeExtensionSubtag(int32_t& state, const char* s, int32_t len)
|
|
{
|
|
const int32_t kStart = 0; // Start, wait for a key or attribute or end
|
|
const int32_t kGotKey = 1; // Got a key, wait for type or key or end
|
|
const int32_t kGotType = 2; // Got a type, wait for key or end
|
|
|
|
switch (state) {
|
|
case kStart:
|
|
if (ultag_isUnicodeLocaleKey(s, len)) {
|
|
state = kGotKey;
|
|
return TRUE;
|
|
}
|
|
if (ultag_isUnicodeLocaleAttribute(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
case kGotKey:
|
|
if (ultag_isUnicodeLocaleKey(s, len)) {
|
|
return TRUE;
|
|
}
|
|
if (_isUnicodeLocaleTypeSubtag(s, len)) {
|
|
state = kGotType;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
case kGotType:
|
|
if (ultag_isUnicodeLocaleKey(s, len)) {
|
|
state = kGotKey;
|
|
return TRUE;
|
|
}
|
|
if (_isUnicodeLocaleTypeSubtag(s, len)) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static UBool
|
|
_isStatefulSepListOf(UBool (*test)(int32_t&, const char*, int32_t), const char* s, int32_t len)
|
|
{
|
|
int32_t state = 0;
|
|
const char* p;
|
|
const char* start = s;
|
|
int32_t subtagLen = 0;
|
|
|
|
if (len < 0) {
|
|
len = (int32_t)uprv_strlen(s);
|
|
}
|
|
|
|
for (p = s; len > 0; p++, len--) {
|
|
if (*p == SEP) {
|
|
if (!test(state, start, subtagLen)) {
|
|
return FALSE;
|
|
}
|
|
subtagLen = 0;
|
|
start = p + 1;
|
|
} else {
|
|
subtagLen++;
|
|
}
|
|
}
|
|
|
|
if (test(state, start, subtagLen) && state >= 0) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isTransformedExtensionSubtags(const char* s, int32_t len)
|
|
{
|
|
return _isStatefulSepListOf(&_isTransformedExtensionSubtag, s, len);
|
|
}
|
|
|
|
U_CFUNC UBool
|
|
ultag_isUnicodeExtensionSubtags(const char* s, int32_t len) {
|
|
return _isStatefulSepListOf(&_isUnicodeExtensionSubtag, s, len);
|
|
}
|
|
|
|
|
|
/*
|
|
* -------------------------------------------------
|
|
*
|
|
* Helper functions
|
|
*
|
|
* -------------------------------------------------
|
|
*/
|
|
|
|
static UBool
|
|
_addVariantToList(VariantListEntry **first, VariantListEntry *var) {
|
|
UBool bAdded = TRUE;
|
|
|
|
if (*first == NULL) {
|
|
var->next = NULL;
|
|
*first = var;
|
|
} else {
|
|
VariantListEntry *prev, *cur;
|
|
int32_t cmp;
|
|
|
|
/* variants order should be preserved */
|
|
prev = NULL;
|
|
cur = *first;
|
|
while (TRUE) {
|
|
if (cur == NULL) {
|
|
prev->next = var;
|
|
var->next = NULL;
|
|
break;
|
|
}
|
|
|
|
/* Checking for duplicate variant */
|
|
cmp = uprv_compareInvCharsAsAscii(var->variant, cur->variant);
|
|
if (cmp == 0) {
|
|
/* duplicated variant */
|
|
bAdded = FALSE;
|
|
break;
|
|
}
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
|
|
return bAdded;
|
|
}
|
|
|
|
static UBool
|
|
_addAttributeToList(AttributeListEntry **first, AttributeListEntry *attr) {
|
|
UBool bAdded = TRUE;
|
|
|
|
if (*first == NULL) {
|
|
attr->next = NULL;
|
|
*first = attr;
|
|
} else {
|
|
AttributeListEntry *prev, *cur;
|
|
int32_t cmp;
|
|
|
|
/* reorder variants in alphabetical order */
|
|
prev = NULL;
|
|
cur = *first;
|
|
while (TRUE) {
|
|
if (cur == NULL) {
|
|
prev->next = attr;
|
|
attr->next = NULL;
|
|
break;
|
|
}
|
|
cmp = uprv_compareInvCharsAsAscii(attr->attribute, cur->attribute);
|
|
if (cmp < 0) {
|
|
if (prev == NULL) {
|
|
*first = attr;
|
|
} else {
|
|
prev->next = attr;
|
|
}
|
|
attr->next = cur;
|
|
break;
|
|
}
|
|
if (cmp == 0) {
|
|
/* duplicated variant */
|
|
bAdded = FALSE;
|
|
break;
|
|
}
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
|
|
return bAdded;
|
|
}
|
|
|
|
|
|
static UBool
|
|
_addExtensionToList(ExtensionListEntry **first, ExtensionListEntry *ext, UBool localeToBCP) {
|
|
UBool bAdded = TRUE;
|
|
|
|
if (*first == NULL) {
|
|
ext->next = NULL;
|
|
*first = ext;
|
|
} else {
|
|
ExtensionListEntry *prev, *cur;
|
|
int32_t cmp;
|
|
|
|
/* reorder variants in alphabetical order */
|
|
prev = NULL;
|
|
cur = *first;
|
|
while (TRUE) {
|
|
if (cur == NULL) {
|
|
prev->next = ext;
|
|
ext->next = NULL;
|
|
break;
|
|
}
|
|
if (localeToBCP) {
|
|
/* special handling for locale to bcp conversion */
|
|
int32_t len, curlen;
|
|
|
|
len = (int32_t)uprv_strlen(ext->key);
|
|
curlen = (int32_t)uprv_strlen(cur->key);
|
|
|
|
if (len == 1 && curlen == 1) {
|
|
if (*(ext->key) == *(cur->key)) {
|
|
cmp = 0;
|
|
} else if (*(ext->key) == PRIVATEUSE) {
|
|
cmp = 1;
|
|
} else if (*(cur->key) == PRIVATEUSE) {
|
|
cmp = -1;
|
|
} else {
|
|
cmp = *(ext->key) - *(cur->key);
|
|
}
|
|
} else if (len == 1) {
|
|
cmp = *(ext->key) - LDMLEXT;
|
|
} else if (curlen == 1) {
|
|
cmp = LDMLEXT - *(cur->key);
|
|
} else {
|
|
cmp = uprv_compareInvCharsAsAscii(ext->key, cur->key);
|
|
/* Both are u extension keys - we need special handling for 'attribute' */
|
|
if (cmp != 0) {
|
|
if (uprv_strcmp(cur->key, LOCALE_ATTRIBUTE_KEY) == 0) {
|
|
cmp = 1;
|
|
} else if (uprv_strcmp(ext->key, LOCALE_ATTRIBUTE_KEY) == 0) {
|
|
cmp = -1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
cmp = uprv_compareInvCharsAsAscii(ext->key, cur->key);
|
|
}
|
|
if (cmp < 0) {
|
|
if (prev == NULL) {
|
|
*first = ext;
|
|
} else {
|
|
prev->next = ext;
|
|
}
|
|
ext->next = cur;
|
|
break;
|
|
}
|
|
if (cmp == 0) {
|
|
/* duplicated extension key */
|
|
bAdded = FALSE;
|
|
break;
|
|
}
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
|
|
return bAdded;
|
|
}
|
|
|
|
static void
|
|
_initializeULanguageTag(ULanguageTag* langtag) {
|
|
int32_t i;
|
|
|
|
langtag->buf = NULL;
|
|
|
|
langtag->language = EMPTY;
|
|
for (i = 0; i < MAXEXTLANG; i++) {
|
|
langtag->extlang[i] = NULL;
|
|
}
|
|
|
|
langtag->script = EMPTY;
|
|
langtag->region = EMPTY;
|
|
|
|
langtag->variants = NULL;
|
|
langtag->extensions = NULL;
|
|
|
|
langtag->legacy = EMPTY;
|
|
langtag->privateuse = EMPTY;
|
|
}
|
|
|
|
static void
|
|
_appendLanguageToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UErrorCode* status) {
|
|
char buf[ULOC_LANG_CAPACITY];
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t len, i;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
len = uloc_getLanguage(localeID, buf, sizeof(buf), &tmpStatus);
|
|
if (U_FAILURE(tmpStatus) || tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
len = 0;
|
|
}
|
|
|
|
/* Note: returned language code is in lower case letters */
|
|
|
|
if (len == 0) {
|
|
sink.Append(LANG_UND, LANG_UND_LEN);
|
|
} else if (!ultag_isLanguageSubtag(buf, len)) {
|
|
/* invalid language code */
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
sink.Append(LANG_UND, LANG_UND_LEN);
|
|
} else {
|
|
/* resolve deprecated */
|
|
for (i = 0; i < UPRV_LENGTHOF(DEPRECATEDLANGS); i += 2) {
|
|
// 2-letter deprecated subtags are listede before 3-letter
|
|
// ones in DEPRECATEDLANGS[]. Get out of loop on coming
|
|
// across the 1st 3-letter subtag, if the input is a 2-letter code.
|
|
// to avoid continuing to try when there's no match.
|
|
if (uprv_strlen(buf) < uprv_strlen(DEPRECATEDLANGS[i])) break;
|
|
if (uprv_compareInvCharsAsAscii(buf, DEPRECATEDLANGS[i]) == 0) {
|
|
uprv_strcpy(buf, DEPRECATEDLANGS[i + 1]);
|
|
len = (int32_t)uprv_strlen(buf);
|
|
break;
|
|
}
|
|
}
|
|
sink.Append(buf, len);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_appendScriptToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UErrorCode* status) {
|
|
char buf[ULOC_SCRIPT_CAPACITY];
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t len;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
len = uloc_getScript(localeID, buf, sizeof(buf), &tmpStatus);
|
|
if (U_FAILURE(tmpStatus) || tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len > 0) {
|
|
if (!ultag_isScriptSubtag(buf, len)) {
|
|
/* invalid script code */
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
} else {
|
|
sink.Append("-", 1);
|
|
sink.Append(buf, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_appendRegionToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UErrorCode* status) {
|
|
char buf[ULOC_COUNTRY_CAPACITY];
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t len;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
len = uloc_getCountry(localeID, buf, sizeof(buf), &tmpStatus);
|
|
if (U_FAILURE(tmpStatus) || tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len > 0) {
|
|
if (!ultag_isRegionSubtag(buf, len)) {
|
|
/* invalid region code */
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
} else {
|
|
sink.Append("-", 1);
|
|
/* resolve deprecated */
|
|
for (int i = 0; i < UPRV_LENGTHOF(DEPRECATEDREGIONS); i += 2) {
|
|
if (uprv_compareInvCharsAsAscii(buf, DEPRECATEDREGIONS[i]) == 0) {
|
|
uprv_strcpy(buf, DEPRECATEDREGIONS[i + 1]);
|
|
len = (int32_t)uprv_strlen(buf);
|
|
break;
|
|
}
|
|
}
|
|
sink.Append(buf, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _sortVariants(VariantListEntry* first) {
|
|
for (VariantListEntry* var1 = first; var1 != NULL; var1 = var1->next) {
|
|
for (VariantListEntry* var2 = var1->next; var2 != NULL; var2 = var2->next) {
|
|
// Swap var1->variant and var2->variant.
|
|
if (uprv_compareInvCharsAsAscii(var1->variant, var2->variant) > 0) {
|
|
const char* temp = var1->variant;
|
|
var1->variant = var2->variant;
|
|
var2->variant = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_appendVariantsToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UBool *hadPosix, UErrorCode* status) {
|
|
char buf[ULOC_FULLNAME_CAPACITY];
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t len, i;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
len = uloc_getVariant(localeID, buf, sizeof(buf), &tmpStatus);
|
|
if (U_FAILURE(tmpStatus) || tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len > 0) {
|
|
char *p, *pVar;
|
|
UBool bNext = TRUE;
|
|
VariantListEntry *var;
|
|
VariantListEntry *varFirst = NULL;
|
|
|
|
pVar = NULL;
|
|
p = buf;
|
|
while (bNext) {
|
|
if (*p == SEP || *p == LOCALE_SEP || *p == 0) {
|
|
if (*p == 0) {
|
|
bNext = FALSE;
|
|
} else {
|
|
*p = 0; /* terminate */
|
|
}
|
|
if (pVar == NULL) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
/* ignore empty variant */
|
|
} else {
|
|
/* ICU uses upper case letters for variants, but
|
|
the canonical format is lowercase in BCP47 */
|
|
for (i = 0; *(pVar + i) != 0; i++) {
|
|
*(pVar + i) = uprv_tolower(*(pVar + i));
|
|
}
|
|
|
|
/* validate */
|
|
if (_isVariantSubtag(pVar, -1)) {
|
|
if (uprv_strcmp(pVar,POSIX_VALUE) || len != (int32_t)uprv_strlen(POSIX_VALUE)) {
|
|
/* emit the variant to the list */
|
|
var = (VariantListEntry*)uprv_malloc(sizeof(VariantListEntry));
|
|
if (var == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
var->variant = pVar;
|
|
if (!_addVariantToList(&varFirst, var)) {
|
|
/* duplicated variant */
|
|
uprv_free(var);
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Special handling for POSIX variant, need to remember that we had it and then */
|
|
/* treat it like an extension later. */
|
|
*hadPosix = TRUE;
|
|
}
|
|
} else if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
} else if (_isPrivateuseValueSubtag(pVar, -1)) {
|
|
/* Handle private use subtags separately */
|
|
break;
|
|
}
|
|
}
|
|
/* reset variant starting position */
|
|
pVar = NULL;
|
|
} else if (pVar == NULL) {
|
|
pVar = p;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
if (U_SUCCESS(*status)) {
|
|
if (varFirst != NULL) {
|
|
int32_t varLen;
|
|
|
|
/* per UTS35, we should sort the variants */
|
|
_sortVariants(varFirst);
|
|
|
|
/* write out validated/normalized variants to the target */
|
|
var = varFirst;
|
|
while (var != NULL) {
|
|
sink.Append("-", 1);
|
|
varLen = (int32_t)uprv_strlen(var->variant);
|
|
sink.Append(var->variant, varLen);
|
|
var = var->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* clean up */
|
|
var = varFirst;
|
|
while (var != NULL) {
|
|
VariantListEntry *tmpVar = var->next;
|
|
uprv_free(var);
|
|
var = tmpVar;
|
|
}
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_appendKeywordsToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UBool hadPosix, UErrorCode* status) {
|
|
char attrBuf[ULOC_KEYWORD_AND_VALUES_CAPACITY] = { 0 };
|
|
int32_t attrBufLength = 0;
|
|
|
|
icu::MemoryPool<AttributeListEntry> attrPool;
|
|
icu::MemoryPool<ExtensionListEntry> extPool;
|
|
icu::MemoryPool<icu::CharString> strPool;
|
|
|
|
icu::LocalUEnumerationPointer keywordEnum(uloc_openKeywords(localeID, status));
|
|
if (U_FAILURE(*status) && !hadPosix) {
|
|
return;
|
|
}
|
|
if (keywordEnum.isValid() || hadPosix) {
|
|
/* reorder extensions */
|
|
int32_t len;
|
|
const char *key;
|
|
ExtensionListEntry *firstExt = NULL;
|
|
ExtensionListEntry *ext;
|
|
AttributeListEntry *firstAttr = NULL;
|
|
AttributeListEntry *attr;
|
|
icu::MemoryPool<icu::CharString> extBufPool;
|
|
const char *bcpKey=nullptr, *bcpValue=nullptr;
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t keylen;
|
|
UBool isBcpUExt;
|
|
|
|
while (TRUE) {
|
|
key = uenum_next(keywordEnum.getAlias(), NULL, status);
|
|
if (key == NULL) {
|
|
break;
|
|
}
|
|
|
|
icu::CharString buf;
|
|
{
|
|
icu::CharStringByteSink sink(&buf);
|
|
ulocimp_getKeywordValue(localeID, key, sink, &tmpStatus);
|
|
}
|
|
len = buf.length();
|
|
|
|
if (U_FAILURE(tmpStatus)) {
|
|
if (tmpStatus == U_MEMORY_ALLOCATION_ERROR) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
/* ignore this keyword */
|
|
tmpStatus = U_ZERO_ERROR;
|
|
continue;
|
|
}
|
|
|
|
keylen = (int32_t)uprv_strlen(key);
|
|
isBcpUExt = (keylen > 1);
|
|
|
|
/* special keyword used for representing Unicode locale attributes */
|
|
if (uprv_strcmp(key, LOCALE_ATTRIBUTE_KEY) == 0) {
|
|
if (len > 0) {
|
|
int32_t i = 0;
|
|
while (TRUE) {
|
|
attrBufLength = 0;
|
|
for (; i < len; i++) {
|
|
if (buf[i] != '-') {
|
|
attrBuf[attrBufLength++] = buf[i];
|
|
} else {
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if (attrBufLength > 0) {
|
|
attrBuf[attrBufLength] = 0;
|
|
|
|
} else if (i >= len){
|
|
break;
|
|
}
|
|
|
|
/* create AttributeListEntry */
|
|
attr = attrPool.create();
|
|
if (attr == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
icu::CharString* attrValue =
|
|
strPool.create(attrBuf, attrBufLength, *status);
|
|
if (attrValue == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
if (U_FAILURE(*status)) {
|
|
break;
|
|
}
|
|
attr->attribute = attrValue->data();
|
|
|
|
if (!_addAttributeToList(&firstAttr, attr)) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* for a place holder ExtensionListEntry */
|
|
bcpKey = LOCALE_ATTRIBUTE_KEY;
|
|
bcpValue = NULL;
|
|
}
|
|
} else if (isBcpUExt) {
|
|
bcpKey = uloc_toUnicodeLocaleKey(key);
|
|
if (bcpKey == NULL) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* we've checked buf is null-terminated above */
|
|
bcpValue = uloc_toUnicodeLocaleType(key, buf.data());
|
|
if (bcpValue == NULL) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (bcpValue == buf.data()) {
|
|
/*
|
|
When uloc_toUnicodeLocaleType(key, buf) returns the
|
|
input value as is, the value is well-formed, but has
|
|
no known mapping. This implementation normalizes the
|
|
value to lower case
|
|
*/
|
|
icu::CharString* extBuf = extBufPool.create(buf, tmpStatus);
|
|
|
|
if (extBuf == nullptr) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
if (U_FAILURE(tmpStatus)) {
|
|
*status = tmpStatus;
|
|
break;
|
|
}
|
|
|
|
T_CString_toLowerCase(extBuf->data());
|
|
bcpValue = extBuf->data();
|
|
}
|
|
} else {
|
|
if (*key == PRIVATEUSE) {
|
|
if (!ultag_isPrivateuseValueSubtags(buf.data(), len)) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
} else {
|
|
if (!_isExtensionSingleton(key, keylen) || !ultag_isExtensionSubtags(buf.data(), len)) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
bcpKey = key;
|
|
icu::CharString* extBuf =
|
|
extBufPool.create(buf.data(), len, tmpStatus);
|
|
if (extBuf == nullptr) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
if (U_FAILURE(tmpStatus)) {
|
|
*status = tmpStatus;
|
|
break;
|
|
}
|
|
bcpValue = extBuf->data();
|
|
}
|
|
|
|
/* create ExtensionListEntry */
|
|
ext = extPool.create();
|
|
if (ext == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
ext->key = bcpKey;
|
|
ext->value = bcpValue;
|
|
|
|
if (!_addExtensionToList(&firstExt, ext, TRUE)) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Special handling for POSIX variant - add the keywords for POSIX */
|
|
if (hadPosix) {
|
|
/* create ExtensionListEntry for POSIX */
|
|
ext = extPool.create();
|
|
if (ext == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
ext->key = POSIX_KEY;
|
|
ext->value = POSIX_VALUE;
|
|
|
|
if (!_addExtensionToList(&firstExt, ext, TRUE)) {
|
|
// Silently ignore errors.
|
|
}
|
|
}
|
|
|
|
if (U_SUCCESS(*status) && (firstExt != NULL || firstAttr != NULL)) {
|
|
UBool startLDMLExtension = FALSE;
|
|
for (ext = firstExt; ext; ext = ext->next) {
|
|
if (!startLDMLExtension && uprv_strlen(ext->key) > 1) {
|
|
/* first LDML u singlton extension */
|
|
sink.Append("-u", 2);
|
|
startLDMLExtension = TRUE;
|
|
}
|
|
|
|
/* write out the sorted BCP47 attributes, extensions and private use */
|
|
if (uprv_strcmp(ext->key, LOCALE_ATTRIBUTE_KEY) == 0) {
|
|
/* write the value for the attributes */
|
|
for (attr = firstAttr; attr; attr = attr->next) {
|
|
sink.Append("-", 1);
|
|
sink.Append(
|
|
attr->attribute, static_cast<int32_t>(uprv_strlen(attr->attribute)));
|
|
}
|
|
} else {
|
|
sink.Append("-", 1);
|
|
sink.Append(ext->key, static_cast<int32_t>(uprv_strlen(ext->key)));
|
|
if (uprv_strcmp(ext->value, "true") != 0 &&
|
|
uprv_strcmp(ext->value, "yes") != 0) {
|
|
sink.Append("-", 1);
|
|
sink.Append(ext->value, static_cast<int32_t>(uprv_strlen(ext->value)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Append keywords parsed from LDML extension value
|
|
* e.g. "u-ca-gregory-co-trad" -> {calendar = gregorian} {collation = traditional}
|
|
* Note: char* buf is used for storing keywords
|
|
*/
|
|
static void
|
|
_appendLDMLExtensionAsKeywords(const char* ldmlext, ExtensionListEntry** appendTo, icu::MemoryPool<ExtensionListEntry>& extPool, icu::MemoryPool<icu::CharString>& kwdBuf, UBool *posixVariant, UErrorCode *status) {
|
|
const char *pTag; /* beginning of current subtag */
|
|
const char *pKwds; /* beginning of key-type pairs */
|
|
UBool variantExists = *posixVariant;
|
|
|
|
ExtensionListEntry *kwdFirst = NULL; /* first LDML keyword */
|
|
ExtensionListEntry *kwd, *nextKwd;
|
|
|
|
int32_t len;
|
|
|
|
/* Reset the posixVariant value */
|
|
*posixVariant = FALSE;
|
|
|
|
pTag = ldmlext;
|
|
pKwds = NULL;
|
|
|
|
{
|
|
AttributeListEntry *attrFirst = NULL; /* first attribute */
|
|
AttributeListEntry *attr, *nextAttr;
|
|
|
|
char attrBuf[ULOC_KEYWORD_AND_VALUES_CAPACITY];
|
|
int32_t attrBufIdx = 0;
|
|
|
|
icu::MemoryPool<AttributeListEntry> attrPool;
|
|
|
|
/* Iterate through u extension attributes */
|
|
while (*pTag) {
|
|
/* locate next separator char */
|
|
for (len = 0; *(pTag + len) && *(pTag + len) != SEP; len++);
|
|
|
|
if (ultag_isUnicodeLocaleKey(pTag, len)) {
|
|
pKwds = pTag;
|
|
break;
|
|
}
|
|
|
|
/* add this attribute to the list */
|
|
attr = attrPool.create();
|
|
if (attr == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
|
|
if (len < (int32_t)sizeof(attrBuf) - attrBufIdx) {
|
|
uprv_memcpy(&attrBuf[attrBufIdx], pTag, len);
|
|
attrBuf[attrBufIdx + len] = 0;
|
|
attr->attribute = &attrBuf[attrBufIdx];
|
|
attrBufIdx += (len + 1);
|
|
} else {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
// duplicate attribute is ignored, causes no error.
|
|
_addAttributeToList(&attrFirst, attr);
|
|
|
|
/* next tag */
|
|
pTag += len;
|
|
if (*pTag) {
|
|
/* next to the separator */
|
|
pTag++;
|
|
}
|
|
}
|
|
|
|
if (attrFirst) {
|
|
/* emit attributes as an LDML keyword, e.g. attribute=attr1-attr2 */
|
|
|
|
kwd = extPool.create();
|
|
if (kwd == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
|
|
icu::CharString* value = kwdBuf.create();
|
|
if (value == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
|
|
/* attribute subtags sorted in alphabetical order as type */
|
|
attr = attrFirst;
|
|
while (attr != NULL) {
|
|
nextAttr = attr->next;
|
|
if (attr != attrFirst) {
|
|
value->append('-', *status);
|
|
}
|
|
value->append(attr->attribute, *status);
|
|
attr = nextAttr;
|
|
}
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
kwd->key = LOCALE_ATTRIBUTE_KEY;
|
|
kwd->value = value->data();
|
|
|
|
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pKwds) {
|
|
const char *pBcpKey = NULL; /* u extenstion key subtag */
|
|
const char *pBcpType = NULL; /* beginning of u extension type subtag(s) */
|
|
int32_t bcpKeyLen = 0;
|
|
int32_t bcpTypeLen = 0;
|
|
UBool isDone = FALSE;
|
|
|
|
pTag = pKwds;
|
|
/* BCP47 representation of LDML key/type pairs */
|
|
while (!isDone) {
|
|
const char *pNextBcpKey = NULL;
|
|
int32_t nextBcpKeyLen = 0;
|
|
UBool emitKeyword = FALSE;
|
|
|
|
if (*pTag) {
|
|
/* locate next separator char */
|
|
for (len = 0; *(pTag + len) && *(pTag + len) != SEP; len++);
|
|
|
|
if (ultag_isUnicodeLocaleKey(pTag, len)) {
|
|
if (pBcpKey) {
|
|
emitKeyword = TRUE;
|
|
pNextBcpKey = pTag;
|
|
nextBcpKeyLen = len;
|
|
} else {
|
|
pBcpKey = pTag;
|
|
bcpKeyLen = len;
|
|
}
|
|
} else {
|
|
U_ASSERT(pBcpKey != NULL);
|
|
/* within LDML type subtags */
|
|
if (pBcpType) {
|
|
bcpTypeLen += (len + 1);
|
|
} else {
|
|
pBcpType = pTag;
|
|
bcpTypeLen = len;
|
|
}
|
|
}
|
|
|
|
/* next tag */
|
|
pTag += len;
|
|
if (*pTag) {
|
|
/* next to the separator */
|
|
pTag++;
|
|
}
|
|
} else {
|
|
/* processing last one */
|
|
emitKeyword = TRUE;
|
|
isDone = TRUE;
|
|
}
|
|
|
|
if (emitKeyword) {
|
|
const char *pKey = NULL; /* LDML key */
|
|
const char *pType = NULL; /* LDML type */
|
|
|
|
char bcpKeyBuf[3]; /* BCP key length is always 2 for now */
|
|
|
|
U_ASSERT(pBcpKey != NULL);
|
|
|
|
if (bcpKeyLen >= (int32_t)sizeof(bcpKeyBuf)) {
|
|
/* the BCP key is invalid */
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
U_ASSERT(bcpKeyLen <= 2);
|
|
|
|
uprv_strncpy(bcpKeyBuf, pBcpKey, bcpKeyLen);
|
|
bcpKeyBuf[bcpKeyLen] = 0;
|
|
|
|
/* u extension key to LDML key */
|
|
pKey = uloc_toLegacyKey(bcpKeyBuf);
|
|
if (pKey == NULL) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
if (pKey == bcpKeyBuf) {
|
|
/*
|
|
The key returned by toLegacyKey points to the input buffer.
|
|
We normalize the result key to lower case.
|
|
*/
|
|
T_CString_toLowerCase(bcpKeyBuf);
|
|
icu::CharString* key = kwdBuf.create(bcpKeyBuf, bcpKeyLen, *status);
|
|
if (key == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
pKey = key->data();
|
|
}
|
|
|
|
if (pBcpType) {
|
|
char bcpTypeBuf[128]; /* practically long enough even considering multiple subtag type */
|
|
if (bcpTypeLen >= (int32_t)sizeof(bcpTypeBuf)) {
|
|
/* the BCP type is too long */
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
uprv_strncpy(bcpTypeBuf, pBcpType, bcpTypeLen);
|
|
bcpTypeBuf[bcpTypeLen] = 0;
|
|
|
|
/* BCP type to locale type */
|
|
pType = uloc_toLegacyType(pKey, bcpTypeBuf);
|
|
if (pType == NULL) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
if (pType == bcpTypeBuf) {
|
|
/*
|
|
The type returned by toLegacyType points to the input buffer.
|
|
We normalize the result type to lower case.
|
|
*/
|
|
/* normalize to lower case */
|
|
T_CString_toLowerCase(bcpTypeBuf);
|
|
icu::CharString* type = kwdBuf.create(bcpTypeBuf, bcpTypeLen, *status);
|
|
if (type == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
pType = type->data();
|
|
}
|
|
} else {
|
|
/* typeless - default type value is "yes" */
|
|
pType = LOCALE_TYPE_YES;
|
|
}
|
|
|
|
/* Special handling for u-va-posix, since we want to treat this as a variant,
|
|
not as a keyword */
|
|
if (!variantExists && !uprv_strcmp(pKey, POSIX_KEY) && !uprv_strcmp(pType, POSIX_VALUE) ) {
|
|
*posixVariant = TRUE;
|
|
} else {
|
|
/* create an ExtensionListEntry for this keyword */
|
|
kwd = extPool.create();
|
|
if (kwd == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
|
|
kwd->key = pKey;
|
|
kwd->value = pType;
|
|
|
|
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
|
|
// duplicate keyword is allowed, Only the first
|
|
// is honored.
|
|
}
|
|
}
|
|
|
|
pBcpKey = pNextBcpKey;
|
|
bcpKeyLen = pNextBcpKey != NULL ? nextBcpKeyLen : 0;
|
|
pBcpType = NULL;
|
|
bcpTypeLen = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
kwd = kwdFirst;
|
|
while (kwd != NULL) {
|
|
nextKwd = kwd->next;
|
|
_addExtensionToList(appendTo, kwd, FALSE);
|
|
kwd = nextKwd;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
_appendKeywords(ULanguageTag* langtag, icu::ByteSink& sink, UErrorCode* status) {
|
|
int32_t i, n;
|
|
int32_t len;
|
|
ExtensionListEntry *kwdFirst = NULL;
|
|
ExtensionListEntry *kwd;
|
|
const char *key, *type;
|
|
icu::MemoryPool<ExtensionListEntry> extPool;
|
|
icu::MemoryPool<icu::CharString> kwdBuf;
|
|
UBool posixVariant = FALSE;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
/* Determine if variants already exists */
|
|
if (ultag_getVariantsSize(langtag)) {
|
|
posixVariant = TRUE;
|
|
}
|
|
|
|
n = ultag_getExtensionsSize(langtag);
|
|
|
|
/* resolve locale keywords and reordering keys */
|
|
for (i = 0; i < n; i++) {
|
|
key = ultag_getExtensionKey(langtag, i);
|
|
type = ultag_getExtensionValue(langtag, i);
|
|
if (*key == LDMLEXT) {
|
|
_appendLDMLExtensionAsKeywords(type, &kwdFirst, extPool, kwdBuf, &posixVariant, status);
|
|
if (U_FAILURE(*status)) {
|
|
break;
|
|
}
|
|
} else {
|
|
kwd = extPool.create();
|
|
if (kwd == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
break;
|
|
}
|
|
kwd->key = key;
|
|
kwd->value = type;
|
|
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (U_SUCCESS(*status)) {
|
|
type = ultag_getPrivateUse(langtag);
|
|
if ((int32_t)uprv_strlen(type) > 0) {
|
|
/* add private use as a keyword */
|
|
kwd = extPool.create();
|
|
if (kwd == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
} else {
|
|
kwd->key = PRIVATEUSE_KEY;
|
|
kwd->value = type;
|
|
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If a POSIX variant was in the extensions, write it out before writing the keywords. */
|
|
|
|
if (U_SUCCESS(*status) && posixVariant) {
|
|
len = (int32_t) uprv_strlen(_POSIX);
|
|
sink.Append(_POSIX, len);
|
|
}
|
|
|
|
if (U_SUCCESS(*status) && kwdFirst != NULL) {
|
|
/* write out the sorted keywords */
|
|
UBool firstValue = TRUE;
|
|
kwd = kwdFirst;
|
|
do {
|
|
if (firstValue) {
|
|
sink.Append("@", 1);
|
|
firstValue = FALSE;
|
|
} else {
|
|
sink.Append(";", 1);
|
|
}
|
|
|
|
/* key */
|
|
len = (int32_t)uprv_strlen(kwd->key);
|
|
sink.Append(kwd->key, len);
|
|
sink.Append("=", 1);
|
|
|
|
/* type */
|
|
len = (int32_t)uprv_strlen(kwd->value);
|
|
sink.Append(kwd->value, len);
|
|
|
|
kwd = kwd->next;
|
|
} while (kwd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_appendPrivateuseToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UBool hadPosix, UErrorCode* status) {
|
|
(void)hadPosix;
|
|
char buf[ULOC_FULLNAME_CAPACITY];
|
|
char tmpAppend[ULOC_FULLNAME_CAPACITY];
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
int32_t len, i;
|
|
int32_t reslen = 0;
|
|
int32_t capacity = sizeof tmpAppend;
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
len = uloc_getVariant(localeID, buf, sizeof(buf), &tmpStatus);
|
|
if (U_FAILURE(tmpStatus) || tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (len > 0) {
|
|
char *p, *pPriv;
|
|
UBool bNext = TRUE;
|
|
UBool firstValue = TRUE;
|
|
UBool writeValue;
|
|
|
|
pPriv = NULL;
|
|
p = buf;
|
|
while (bNext) {
|
|
writeValue = FALSE;
|
|
if (*p == SEP || *p == LOCALE_SEP || *p == 0) {
|
|
if (*p == 0) {
|
|
bNext = FALSE;
|
|
} else {
|
|
*p = 0; /* terminate */
|
|
}
|
|
if (pPriv != NULL) {
|
|
/* Private use in the canonical format is lowercase in BCP47 */
|
|
for (i = 0; *(pPriv + i) != 0; i++) {
|
|
*(pPriv + i) = uprv_tolower(*(pPriv + i));
|
|
}
|
|
|
|
/* validate */
|
|
if (_isPrivateuseValueSubtag(pPriv, -1)) {
|
|
if (firstValue) {
|
|
if (!_isVariantSubtag(pPriv, -1)) {
|
|
writeValue = TRUE;
|
|
}
|
|
} else {
|
|
writeValue = TRUE;
|
|
}
|
|
} else if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (writeValue) {
|
|
if (reslen < capacity) {
|
|
tmpAppend[reslen++] = SEP;
|
|
}
|
|
|
|
if (firstValue) {
|
|
if (reslen < capacity) {
|
|
tmpAppend[reslen++] = *PRIVATEUSE_KEY;
|
|
}
|
|
|
|
if (reslen < capacity) {
|
|
tmpAppend[reslen++] = SEP;
|
|
}
|
|
|
|
len = (int32_t)uprv_strlen(PRIVUSE_VARIANT_PREFIX);
|
|
if (reslen < capacity) {
|
|
uprv_memcpy(tmpAppend + reslen, PRIVUSE_VARIANT_PREFIX, uprv_min(len, capacity - reslen));
|
|
}
|
|
reslen += len;
|
|
|
|
if (reslen < capacity) {
|
|
tmpAppend[reslen++] = SEP;
|
|
}
|
|
|
|
firstValue = FALSE;
|
|
}
|
|
|
|
len = (int32_t)uprv_strlen(pPriv);
|
|
if (reslen < capacity) {
|
|
uprv_memcpy(tmpAppend + reslen, pPriv, uprv_min(len, capacity - reslen));
|
|
}
|
|
reslen += len;
|
|
}
|
|
}
|
|
/* reset private use starting position */
|
|
pPriv = NULL;
|
|
} else if (pPriv == NULL) {
|
|
pPriv = p;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (U_SUCCESS(*status)) {
|
|
len = reslen;
|
|
sink.Append(tmpAppend, len);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* -------------------------------------------------
|
|
*
|
|
* ultag_ functions
|
|
*
|
|
* -------------------------------------------------
|
|
*/
|
|
|
|
/* Bit flags used by the parser */
|
|
#define LANG 0x0001
|
|
#define EXTL 0x0002
|
|
#define SCRT 0x0004
|
|
#define REGN 0x0008
|
|
#define VART 0x0010
|
|
#define EXTS 0x0020
|
|
#define EXTV 0x0040
|
|
#define PRIV 0x0080
|
|
|
|
/**
|
|
* Ticket #12705 - The optimizer in Visual Studio 2015 Update 3 has problems optimizing this function.
|
|
* As a work-around, optimization is disabled for this function on VS2015 and VS2017.
|
|
* This work-around should be removed once the following versions of Visual Studio are no
|
|
* longer supported: All versions of VS2015/VS2017, and versions of VS2019 below 16.4.
|
|
*/
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && (_MSC_VER < 1924)
|
|
#pragma optimize( "", off )
|
|
#endif
|
|
|
|
static ULanguageTag*
|
|
ultag_parse(const char* tag, int32_t tagLen, int32_t* parsedLen, UErrorCode* status) {
|
|
char *tagBuf;
|
|
int16_t next;
|
|
char *pSubtag, *pNext, *pLastGoodPosition;
|
|
int32_t subtagLen;
|
|
int32_t extlangIdx;
|
|
ExtensionListEntry *pExtension;
|
|
char *pExtValueSubtag, *pExtValueSubtagEnd;
|
|
int32_t i;
|
|
UBool privateuseVar = FALSE;
|
|
int32_t legacyLen = 0;
|
|
|
|
if (parsedLen != NULL) {
|
|
*parsedLen = 0;
|
|
}
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (tagLen < 0) {
|
|
tagLen = (int32_t)uprv_strlen(tag);
|
|
}
|
|
|
|
/* copy the entire string */
|
|
tagBuf = (char*)uprv_malloc(tagLen + 1);
|
|
if (tagBuf == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
uprv_memcpy(tagBuf, tag, tagLen);
|
|
*(tagBuf + tagLen) = 0;
|
|
|
|
/* create a ULanguageTag */
|
|
icu::LocalULanguageTagPointer t(
|
|
(ULanguageTag*)uprv_malloc(sizeof(ULanguageTag)));
|
|
if (t.isNull()) {
|
|
uprv_free(tagBuf);
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
_initializeULanguageTag(t.getAlias());
|
|
t->buf = tagBuf;
|
|
|
|
if (tagLen < MINLEN) {
|
|
/* the input tag is too short - return empty ULanguageTag */
|
|
return t.orphan();
|
|
}
|
|
|
|
size_t parsedLenDelta = 0;
|
|
// Legacy tag will be consider together. Legacy tag with intervening
|
|
// script and region such as art-DE-lojban or art-Latn-lojban won't be
|
|
// matched.
|
|
/* check if the tag is legacy */
|
|
for (i = 0; i < UPRV_LENGTHOF(LEGACY); i += 2) {
|
|
int32_t checkLegacyLen = static_cast<int32_t>(uprv_strlen(LEGACY[i]));
|
|
if (tagLen < checkLegacyLen) {
|
|
continue;
|
|
}
|
|
if (tagLen > checkLegacyLen && tagBuf[checkLegacyLen] != '-') {
|
|
// make sure next char is '-'.
|
|
continue;
|
|
}
|
|
if (uprv_strnicmp(LEGACY[i], tagBuf, checkLegacyLen) == 0) {
|
|
int32_t newTagLength;
|
|
|
|
legacyLen = checkLegacyLen; /* back up for output parsedLen */
|
|
int32_t replacementLen = static_cast<int32_t>(uprv_strlen(LEGACY[i+1]));
|
|
newTagLength = replacementLen + tagLen - checkLegacyLen;
|
|
if (tagLen < newTagLength) {
|
|
uprv_free(tagBuf);
|
|
tagBuf = (char*)uprv_malloc(newTagLength + 1);
|
|
if (tagBuf == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
t->buf = tagBuf;
|
|
tagLen = newTagLength;
|
|
}
|
|
parsedLenDelta = checkLegacyLen - replacementLen;
|
|
uprv_strcpy(t->buf, LEGACY[i + 1]);
|
|
if (checkLegacyLen != tagLen) {
|
|
uprv_strcpy(t->buf + replacementLen, tag + checkLegacyLen);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (legacyLen == 0) {
|
|
for (i = 0; i < UPRV_LENGTHOF(REDUNDANT); i += 2) {
|
|
const char* redundantTag = REDUNDANT[i];
|
|
size_t redundantTagLen = uprv_strlen(redundantTag);
|
|
// The preferred tag for a redundant tag is always shorter than redundant
|
|
// tag. A redundant tag may or may not be followed by other subtags.
|
|
// (i.e. "zh-yue" or "zh-yue-u-co-pinyin").
|
|
if (uprv_strnicmp(redundantTag, tagBuf, static_cast<uint32_t>(redundantTagLen)) == 0) {
|
|
const char* redundantTagEnd = tagBuf + redundantTagLen;
|
|
if (*redundantTagEnd == '\0' || *redundantTagEnd == SEP) {
|
|
const char* preferredTag = REDUNDANT[i + 1];
|
|
size_t preferredTagLen = uprv_strlen(preferredTag);
|
|
uprv_strncpy(t->buf, preferredTag, preferredTagLen);
|
|
if (*redundantTagEnd == SEP) {
|
|
uprv_memmove(tagBuf + preferredTagLen,
|
|
redundantTagEnd,
|
|
tagLen - redundantTagLen + 1);
|
|
} else {
|
|
tagBuf[preferredTagLen] = '\0';
|
|
}
|
|
// parsedLen should be the length of the input
|
|
// before redundantTag is replaced by preferredTag.
|
|
// Save the delta to add it back later.
|
|
parsedLenDelta = redundantTagLen - preferredTagLen;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* langtag = language
|
|
* ["-" script]
|
|
* ["-" region]
|
|
* *("-" variant)
|
|
* *("-" extension)
|
|
* ["-" privateuse]
|
|
*/
|
|
|
|
next = LANG | PRIV;
|
|
pNext = pLastGoodPosition = tagBuf;
|
|
extlangIdx = 0;
|
|
pExtension = NULL;
|
|
pExtValueSubtag = NULL;
|
|
pExtValueSubtagEnd = NULL;
|
|
|
|
while (pNext) {
|
|
char *pSep;
|
|
|
|
pSubtag = pNext;
|
|
|
|
/* locate next separator char */
|
|
pSep = pSubtag;
|
|
while (*pSep) {
|
|
if (*pSep == SEP) {
|
|
break;
|
|
}
|
|
pSep++;
|
|
}
|
|
if (*pSep == 0) {
|
|
/* last subtag */
|
|
pNext = NULL;
|
|
} else {
|
|
pNext = pSep + 1;
|
|
}
|
|
subtagLen = (int32_t)(pSep - pSubtag);
|
|
|
|
if (next & LANG) {
|
|
if (ultag_isLanguageSubtag(pSubtag, subtagLen)) {
|
|
*pSep = 0; /* terminate */
|
|
// TODO: move deprecated language code handling here.
|
|
t->language = T_CString_toLowerCase(pSubtag);
|
|
|
|
pLastGoodPosition = pSep;
|
|
next = SCRT | REGN | VART | EXTS | PRIV;
|
|
if (subtagLen <= 3)
|
|
next |= EXTL;
|
|
continue;
|
|
}
|
|
}
|
|
if (next & EXTL) {
|
|
if (_isExtlangSubtag(pSubtag, subtagLen)) {
|
|
*pSep = 0;
|
|
t->extlang[extlangIdx++] = T_CString_toLowerCase(pSubtag);
|
|
|
|
pLastGoodPosition = pSep;
|
|
if (extlangIdx < 3) {
|
|
next = EXTL | SCRT | REGN | VART | EXTS | PRIV;
|
|
} else {
|
|
next = SCRT | REGN | VART | EXTS | PRIV;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if (next & SCRT) {
|
|
if (ultag_isScriptSubtag(pSubtag, subtagLen)) {
|
|
char *p = pSubtag;
|
|
|
|
*pSep = 0;
|
|
|
|
/* to title case */
|
|
*p = uprv_toupper(*p);
|
|
p++;
|
|
for (; *p; p++) {
|
|
*p = uprv_tolower(*p);
|
|
}
|
|
|
|
t->script = pSubtag;
|
|
|
|
pLastGoodPosition = pSep;
|
|
next = REGN | VART | EXTS | PRIV;
|
|
continue;
|
|
}
|
|
}
|
|
if (next & REGN) {
|
|
if (ultag_isRegionSubtag(pSubtag, subtagLen)) {
|
|
*pSep = 0;
|
|
// TODO: move deprecated region code handling here.
|
|
t->region = T_CString_toUpperCase(pSubtag);
|
|
|
|
pLastGoodPosition = pSep;
|
|
next = VART | EXTS | PRIV;
|
|
continue;
|
|
}
|
|
}
|
|
if (next & VART) {
|
|
if (_isVariantSubtag(pSubtag, subtagLen) ||
|
|
(privateuseVar && _isPrivateuseVariantSubtag(pSubtag, subtagLen))) {
|
|
VariantListEntry *var;
|
|
UBool isAdded;
|
|
|
|
var = (VariantListEntry*)uprv_malloc(sizeof(VariantListEntry));
|
|
if (var == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
*pSep = 0;
|
|
var->variant = T_CString_toUpperCase(pSubtag);
|
|
isAdded = _addVariantToList(&(t->variants), var);
|
|
if (!isAdded) {
|
|
/* duplicated variant entry */
|
|
uprv_free(var);
|
|
break;
|
|
}
|
|
pLastGoodPosition = pSep;
|
|
next = VART | EXTS | PRIV;
|
|
continue;
|
|
}
|
|
}
|
|
if (next & EXTS) {
|
|
if (_isExtensionSingleton(pSubtag, subtagLen)) {
|
|
if (pExtension != NULL) {
|
|
if (pExtValueSubtag == NULL || pExtValueSubtagEnd == NULL) {
|
|
/* the previous extension is incomplete */
|
|
uprv_free(pExtension);
|
|
pExtension = NULL;
|
|
break;
|
|
}
|
|
|
|
/* terminate the previous extension value */
|
|
*pExtValueSubtagEnd = 0;
|
|
pExtension->value = T_CString_toLowerCase(pExtValueSubtag);
|
|
|
|
/* insert the extension to the list */
|
|
if (_addExtensionToList(&(t->extensions), pExtension, FALSE)) {
|
|
pLastGoodPosition = pExtValueSubtagEnd;
|
|
} else {
|
|
/* stop parsing here */
|
|
uprv_free(pExtension);
|
|
pExtension = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* create a new extension */
|
|
pExtension = (ExtensionListEntry*)uprv_malloc(sizeof(ExtensionListEntry));
|
|
if (pExtension == NULL) {
|
|
*status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
*pSep = 0;
|
|
pExtension->key = T_CString_toLowerCase(pSubtag);
|
|
pExtension->value = NULL; /* will be set later */
|
|
|
|
/*
|
|
* reset the start and the end location of extension value
|
|
* subtags for this extension
|
|
*/
|
|
pExtValueSubtag = NULL;
|
|
pExtValueSubtagEnd = NULL;
|
|
|
|
next = EXTV;
|
|
continue;
|
|
}
|
|
}
|
|
if (next & EXTV) {
|
|
if (_isExtensionSubtag(pSubtag, subtagLen)) {
|
|
if (pExtValueSubtag == NULL) {
|
|
/* if the start postion of this extension's value is not yet,
|
|
this one is the first value subtag */
|
|
pExtValueSubtag = pSubtag;
|
|
}
|
|
|
|
/* Mark the end of this subtag */
|
|
pExtValueSubtagEnd = pSep;
|
|
next = EXTS | EXTV | PRIV;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
if (next & PRIV) {
|
|
if (uprv_tolower(*pSubtag) == PRIVATEUSE && subtagLen == 1) {
|
|
char *pPrivuseVal;
|
|
|
|
if (pExtension != NULL) {
|
|
/* Process the last extension */
|
|
if (pExtValueSubtag == NULL || pExtValueSubtagEnd == NULL) {
|
|
/* the previous extension is incomplete */
|
|
uprv_free(pExtension);
|
|
pExtension = NULL;
|
|
break;
|
|
} else {
|
|
/* terminate the previous extension value */
|
|
*pExtValueSubtagEnd = 0;
|
|
pExtension->value = T_CString_toLowerCase(pExtValueSubtag);
|
|
|
|
/* insert the extension to the list */
|
|
if (_addExtensionToList(&(t->extensions), pExtension, FALSE)) {
|
|
pLastGoodPosition = pExtValueSubtagEnd;
|
|
pExtension = NULL;
|
|
} else {
|
|
/* stop parsing here */
|
|
uprv_free(pExtension);
|
|
pExtension = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The rest of part will be private use value subtags */
|
|
if (pNext == NULL) {
|
|
/* empty private use subtag */
|
|
break;
|
|
}
|
|
/* back up the private use value start position */
|
|
pPrivuseVal = pNext;
|
|
|
|
/* validate private use value subtags */
|
|
while (pNext) {
|
|
pSubtag = pNext;
|
|
pSep = pSubtag;
|
|
while (*pSep) {
|
|
if (*pSep == SEP) {
|
|
break;
|
|
}
|
|
pSep++;
|
|
}
|
|
if (*pSep == 0) {
|
|
/* last subtag */
|
|
pNext = NULL;
|
|
} else {
|
|
pNext = pSep + 1;
|
|
}
|
|
subtagLen = (int32_t)(pSep - pSubtag);
|
|
|
|
if (uprv_strncmp(pSubtag, PRIVUSE_VARIANT_PREFIX, uprv_strlen(PRIVUSE_VARIANT_PREFIX)) == 0) {
|
|
*pSep = 0;
|
|
next = VART;
|
|
privateuseVar = TRUE;
|
|
break;
|
|
} else if (_isPrivateuseValueSubtag(pSubtag, subtagLen)) {
|
|
pLastGoodPosition = pSep;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (next == VART) {
|
|
continue;
|
|
}
|
|
|
|
if (pLastGoodPosition - pPrivuseVal > 0) {
|
|
*pLastGoodPosition = 0;
|
|
t->privateuse = T_CString_toLowerCase(pPrivuseVal);
|
|
}
|
|
/* No more subtags, exiting the parse loop */
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* If we fell through here, it means this subtag is illegal - quit parsing */
|
|
break;
|
|
}
|
|
|
|
if (pExtension != NULL) {
|
|
/* Process the last extension */
|
|
if (pExtValueSubtag == NULL || pExtValueSubtagEnd == NULL) {
|
|
/* the previous extension is incomplete */
|
|
uprv_free(pExtension);
|
|
} else {
|
|
/* terminate the previous extension value */
|
|
*pExtValueSubtagEnd = 0;
|
|
pExtension->value = T_CString_toLowerCase(pExtValueSubtag);
|
|
/* insert the extension to the list */
|
|
if (_addExtensionToList(&(t->extensions), pExtension, FALSE)) {
|
|
pLastGoodPosition = pExtValueSubtagEnd;
|
|
} else {
|
|
uprv_free(pExtension);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parsedLen != NULL) {
|
|
*parsedLen = (int32_t)(pLastGoodPosition - t->buf + parsedLenDelta);
|
|
}
|
|
|
|
return t.orphan();
|
|
}
|
|
|
|
// Ticket #12705 - Turn optimization back on.
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && (_MSC_VER < 1924)
|
|
#pragma optimize( "", on )
|
|
#endif
|
|
|
|
static void
|
|
ultag_close(ULanguageTag* langtag) {
|
|
|
|
if (langtag == NULL) {
|
|
return;
|
|
}
|
|
|
|
uprv_free(langtag->buf);
|
|
|
|
if (langtag->variants) {
|
|
VariantListEntry *curVar = langtag->variants;
|
|
while (curVar) {
|
|
VariantListEntry *nextVar = curVar->next;
|
|
uprv_free(curVar);
|
|
curVar = nextVar;
|
|
}
|
|
}
|
|
|
|
if (langtag->extensions) {
|
|
ExtensionListEntry *curExt = langtag->extensions;
|
|
while (curExt) {
|
|
ExtensionListEntry *nextExt = curExt->next;
|
|
uprv_free(curExt);
|
|
curExt = nextExt;
|
|
}
|
|
}
|
|
|
|
uprv_free(langtag);
|
|
}
|
|
|
|
static const char*
|
|
ultag_getLanguage(const ULanguageTag* langtag) {
|
|
return langtag->language;
|
|
}
|
|
|
|
#if 0
|
|
static const char*
|
|
ultag_getJDKLanguage(const ULanguageTag* langtag) {
|
|
int32_t i;
|
|
for (i = 0; DEPRECATEDLANGS[i] != NULL; i += 2) {
|
|
if (uprv_compareInvCharsAsAscii(DEPRECATEDLANGS[i], langtag->language) == 0) {
|
|
return DEPRECATEDLANGS[i + 1];
|
|
}
|
|
}
|
|
return langtag->language;
|
|
}
|
|
#endif
|
|
|
|
static const char*
|
|
ultag_getExtlang(const ULanguageTag* langtag, int32_t idx) {
|
|
if (idx >= 0 && idx < MAXEXTLANG) {
|
|
return langtag->extlang[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int32_t
|
|
ultag_getExtlangSize(const ULanguageTag* langtag) {
|
|
int32_t size = 0;
|
|
int32_t i;
|
|
for (i = 0; i < MAXEXTLANG; i++) {
|
|
if (langtag->extlang[i]) {
|
|
size++;
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getScript(const ULanguageTag* langtag) {
|
|
return langtag->script;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getRegion(const ULanguageTag* langtag) {
|
|
return langtag->region;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getVariant(const ULanguageTag* langtag, int32_t idx) {
|
|
const char *var = NULL;
|
|
VariantListEntry *cur = langtag->variants;
|
|
int32_t i = 0;
|
|
while (cur) {
|
|
if (i == idx) {
|
|
var = cur->variant;
|
|
break;
|
|
}
|
|
cur = cur->next;
|
|
i++;
|
|
}
|
|
return var;
|
|
}
|
|
|
|
static int32_t
|
|
ultag_getVariantsSize(const ULanguageTag* langtag) {
|
|
int32_t size = 0;
|
|
VariantListEntry *cur = langtag->variants;
|
|
while (TRUE) {
|
|
if (cur == NULL) {
|
|
break;
|
|
}
|
|
size++;
|
|
cur = cur->next;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getExtensionKey(const ULanguageTag* langtag, int32_t idx) {
|
|
const char *key = NULL;
|
|
ExtensionListEntry *cur = langtag->extensions;
|
|
int32_t i = 0;
|
|
while (cur) {
|
|
if (i == idx) {
|
|
key = cur->key;
|
|
break;
|
|
}
|
|
cur = cur->next;
|
|
i++;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getExtensionValue(const ULanguageTag* langtag, int32_t idx) {
|
|
const char *val = NULL;
|
|
ExtensionListEntry *cur = langtag->extensions;
|
|
int32_t i = 0;
|
|
while (cur) {
|
|
if (i == idx) {
|
|
val = cur->value;
|
|
break;
|
|
}
|
|
cur = cur->next;
|
|
i++;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int32_t
|
|
ultag_getExtensionsSize(const ULanguageTag* langtag) {
|
|
int32_t size = 0;
|
|
ExtensionListEntry *cur = langtag->extensions;
|
|
while (TRUE) {
|
|
if (cur == NULL) {
|
|
break;
|
|
}
|
|
size++;
|
|
cur = cur->next;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static const char*
|
|
ultag_getPrivateUse(const ULanguageTag* langtag) {
|
|
return langtag->privateuse;
|
|
}
|
|
|
|
#if 0
|
|
static const char*
|
|
ultag_getLegacy(const ULanguageTag* langtag) {
|
|
return langtag->legacy;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* -------------------------------------------------
|
|
*
|
|
* Locale/BCP47 conversion APIs, exposed as uloc_*
|
|
*
|
|
* -------------------------------------------------
|
|
*/
|
|
U_CAPI int32_t U_EXPORT2
|
|
uloc_toLanguageTag(const char* localeID,
|
|
char* langtag,
|
|
int32_t langtagCapacity,
|
|
UBool strict,
|
|
UErrorCode* status) {
|
|
if (U_FAILURE(*status)) {
|
|
return 0;
|
|
}
|
|
|
|
icu::CheckedArrayByteSink sink(langtag, langtagCapacity);
|
|
ulocimp_toLanguageTag(localeID, sink, strict, status);
|
|
|
|
int32_t reslen = sink.NumberOfBytesAppended();
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return reslen;
|
|
}
|
|
|
|
if (sink.Overflowed()) {
|
|
*status = U_BUFFER_OVERFLOW_ERROR;
|
|
} else {
|
|
u_terminateChars(langtag, langtagCapacity, reslen, status);
|
|
}
|
|
|
|
return reslen;
|
|
}
|
|
|
|
|
|
U_CAPI void U_EXPORT2
|
|
ulocimp_toLanguageTag(const char* localeID,
|
|
icu::ByteSink& sink,
|
|
UBool strict,
|
|
UErrorCode* status) {
|
|
icu::CharString canonical;
|
|
int32_t reslen;
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
UBool hadPosix = FALSE;
|
|
const char* pKeywordStart;
|
|
|
|
/* Note: uloc_canonicalize returns "en_US_POSIX" for input locale ID "". See #6835 */
|
|
int32_t resultCapacity = static_cast<int32_t>(uprv_strlen(localeID));
|
|
if (resultCapacity > 0) {
|
|
char* buffer;
|
|
|
|
for (;;) {
|
|
buffer = canonical.getAppendBuffer(
|
|
/*minCapacity=*/resultCapacity,
|
|
/*desiredCapacityHint=*/resultCapacity,
|
|
resultCapacity,
|
|
tmpStatus);
|
|
|
|
if (U_FAILURE(tmpStatus)) {
|
|
*status = tmpStatus;
|
|
return;
|
|
}
|
|
|
|
reslen =
|
|
uloc_canonicalize(localeID, buffer, resultCapacity, &tmpStatus);
|
|
|
|
if (tmpStatus != U_BUFFER_OVERFLOW_ERROR) {
|
|
break;
|
|
}
|
|
|
|
resultCapacity = reslen;
|
|
tmpStatus = U_ZERO_ERROR;
|
|
}
|
|
|
|
if (U_FAILURE(tmpStatus)) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
canonical.append(buffer, reslen, tmpStatus);
|
|
if (tmpStatus == U_STRING_NOT_TERMINATED_WARNING) {
|
|
tmpStatus = U_ZERO_ERROR; // Terminators provided by CharString.
|
|
}
|
|
|
|
if (U_FAILURE(tmpStatus)) {
|
|
*status = tmpStatus;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* For handling special case - private use only tag */
|
|
pKeywordStart = locale_getKeywordsStart(canonical.data());
|
|
if (pKeywordStart == canonical.data()) {
|
|
int kwdCnt = 0;
|
|
UBool done = FALSE;
|
|
|
|
icu::LocalUEnumerationPointer kwdEnum(uloc_openKeywords(canonical.data(), &tmpStatus));
|
|
if (U_SUCCESS(tmpStatus)) {
|
|
kwdCnt = uenum_count(kwdEnum.getAlias(), &tmpStatus);
|
|
if (kwdCnt == 1) {
|
|
const char *key;
|
|
int32_t len = 0;
|
|
|
|
key = uenum_next(kwdEnum.getAlias(), &len, &tmpStatus);
|
|
if (len == 1 && *key == PRIVATEUSE) {
|
|
icu::CharString buf;
|
|
{
|
|
icu::CharStringByteSink sink(&buf);
|
|
ulocimp_getKeywordValue(localeID, key, sink, &tmpStatus);
|
|
}
|
|
if (U_SUCCESS(tmpStatus)) {
|
|
if (ultag_isPrivateuseValueSubtags(buf.data(), buf.length())) {
|
|
/* return private use only tag */
|
|
static const char PREFIX[] = { PRIVATEUSE, SEP };
|
|
sink.Append(PREFIX, sizeof(PREFIX));
|
|
sink.Append(buf.data(), buf.length());
|
|
done = TRUE;
|
|
} else if (strict) {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
done = TRUE;
|
|
}
|
|
/* if not strict mode, then "und" will be returned */
|
|
} else {
|
|
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
done = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (done) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
_appendLanguageToLanguageTag(canonical.data(), sink, strict, status);
|
|
_appendScriptToLanguageTag(canonical.data(), sink, strict, status);
|
|
_appendRegionToLanguageTag(canonical.data(), sink, strict, status);
|
|
_appendVariantsToLanguageTag(canonical.data(), sink, strict, &hadPosix, status);
|
|
_appendKeywordsToLanguageTag(canonical.data(), sink, strict, hadPosix, status);
|
|
_appendPrivateuseToLanguageTag(canonical.data(), sink, strict, hadPosix, status);
|
|
}
|
|
|
|
|
|
U_CAPI int32_t U_EXPORT2
|
|
uloc_forLanguageTag(const char* langtag,
|
|
char* localeID,
|
|
int32_t localeIDCapacity,
|
|
int32_t* parsedLength,
|
|
UErrorCode* status) {
|
|
if (U_FAILURE(*status)) {
|
|
return 0;
|
|
}
|
|
|
|
icu::CheckedArrayByteSink sink(localeID, localeIDCapacity);
|
|
ulocimp_forLanguageTag(langtag, -1, sink, parsedLength, status);
|
|
|
|
int32_t reslen = sink.NumberOfBytesAppended();
|
|
|
|
if (U_FAILURE(*status)) {
|
|
return reslen;
|
|
}
|
|
|
|
if (sink.Overflowed()) {
|
|
*status = U_BUFFER_OVERFLOW_ERROR;
|
|
} else {
|
|
u_terminateChars(localeID, localeIDCapacity, reslen, status);
|
|
}
|
|
|
|
return reslen;
|
|
}
|
|
|
|
|
|
U_CAPI void U_EXPORT2
|
|
ulocimp_forLanguageTag(const char* langtag,
|
|
int32_t tagLen,
|
|
icu::ByteSink& sink,
|
|
int32_t* parsedLength,
|
|
UErrorCode* status) {
|
|
UBool isEmpty = TRUE;
|
|
const char *subtag, *p;
|
|
int32_t len;
|
|
int32_t i, n;
|
|
UBool noRegion = TRUE;
|
|
|
|
icu::LocalULanguageTagPointer lt(ultag_parse(langtag, tagLen, parsedLength, status));
|
|
if (U_FAILURE(*status)) {
|
|
return;
|
|
}
|
|
|
|
/* language */
|
|
subtag = ultag_getExtlangSize(lt.getAlias()) > 0 ? ultag_getExtlang(lt.getAlias(), 0) : ultag_getLanguage(lt.getAlias());
|
|
if (uprv_compareInvCharsAsAscii(subtag, LANG_UND) != 0) {
|
|
len = (int32_t)uprv_strlen(subtag);
|
|
if (len > 0) {
|
|
sink.Append(subtag, len);
|
|
isEmpty = FALSE;
|
|
}
|
|
}
|
|
|
|
/* script */
|
|
subtag = ultag_getScript(lt.getAlias());
|
|
len = (int32_t)uprv_strlen(subtag);
|
|
if (len > 0) {
|
|
sink.Append("_", 1);
|
|
isEmpty = FALSE;
|
|
|
|
/* write out the script in title case */
|
|
char c = uprv_toupper(*subtag);
|
|
sink.Append(&c, 1);
|
|
sink.Append(subtag + 1, len - 1);
|
|
}
|
|
|
|
/* region */
|
|
subtag = ultag_getRegion(lt.getAlias());
|
|
len = (int32_t)uprv_strlen(subtag);
|
|
if (len > 0) {
|
|
sink.Append("_", 1);
|
|
isEmpty = FALSE;
|
|
|
|
/* write out the region in upper case */
|
|
p = subtag;
|
|
while (*p) {
|
|
char c = uprv_toupper(*p);
|
|
sink.Append(&c, 1);
|
|
p++;
|
|
}
|
|
noRegion = FALSE;
|
|
}
|
|
|
|
/* variants */
|
|
_sortVariants(lt.getAlias()->variants);
|
|
n = ultag_getVariantsSize(lt.getAlias());
|
|
if (n > 0) {
|
|
if (noRegion) {
|
|
sink.Append("_", 1);
|
|
isEmpty = FALSE;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
subtag = ultag_getVariant(lt.getAlias(), i);
|
|
sink.Append("_", 1);
|
|
|
|
/* write out the variant in upper case */
|
|
p = subtag;
|
|
while (*p) {
|
|
char c = uprv_toupper(*p);
|
|
sink.Append(&c, 1);
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* keywords */
|
|
n = ultag_getExtensionsSize(lt.getAlias());
|
|
subtag = ultag_getPrivateUse(lt.getAlias());
|
|
if (n > 0 || uprv_strlen(subtag) > 0) {
|
|
if (isEmpty && n > 0) {
|
|
/* need a language */
|
|
sink.Append(LANG_UND, LANG_UND_LEN);
|
|
}
|
|
_appendKeywords(lt.getAlias(), sink, status);
|
|
}
|
|
}
|