ntdll: Reimplement RtlLocaleNameToLcid() using the locale.nls data.

Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Alexandre Julliard 2022-03-22 10:31:58 +01:00
parent 4b0f1163fa
commit b631c14702
2 changed files with 91 additions and 145 deletions

View File

@ -2673,15 +2673,6 @@ static void test_LocaleNameToLCID(void)
WCHAR buffer[LOCALE_NAME_MAX_LENGTH];
const struct neutralsublang_name_t *ptr;
static const WCHAR enW[] = {'e','n',0};
static const WCHAR esesW[] = {'e','s','-','e','s',0};
static const WCHAR zhHansW[] = {'z','h','-','H','a','n','s',0};
static const WCHAR zhhansW[] = {'z','h','-','h','a','n','s',0};
static const WCHAR zhHantW[] = {'z','h','-','H','a','n','t',0};
static const WCHAR zhhantW[] = {'z','h','-','h','a','n','t',0};
static const WCHAR zhcnW[] = {'z','h','-','C','N',0};
static const WCHAR zhhkW[] = {'z','h','-','H','K',0};
if (!pLocaleNameToLCID)
{
win_skip( "LocaleNameToLCID not available\n" );
@ -2722,14 +2713,14 @@ static void test_LocaleNameToLCID(void)
"Expected lcid == 0, got %08lx, error %ld\n", lcid, GetLastError());
/* lower-case */
lcid = pLocaleNameToLCID(esesW, 0);
lcid = pLocaleNameToLCID(L"es-es", 0);
ok(lcid == MAKELCID(MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MODERN), SORT_DEFAULT), "Got wrong lcid for es-es: 0x%lx\n", lcid);
/* english neutral name */
lcid = pLocaleNameToLCID(enW, LOCALE_ALLOW_NEUTRAL_NAMES);
lcid = pLocaleNameToLCID(L"en", LOCALE_ALLOW_NEUTRAL_NAMES);
ok(lcid == MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL), SORT_DEFAULT) ||
broken(lcid == 0) /* Vista */, "got 0x%04lx\n", lcid);
lcid = pLocaleNameToLCID(enW, 0);
lcid = pLocaleNameToLCID(L"en", 0);
ok(lcid == MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) ||
broken(lcid == 0) /* Vista */, "got 0x%04lx\n", lcid);
if (lcid)
@ -2749,52 +2740,52 @@ static void test_LocaleNameToLCID(void)
}
/* zh-Hant has LCID 0x7c04, but LocaleNameToLCID actually returns 0x0c04, which is the LCID of zh-HK */
lcid = pLocaleNameToLCID(zhHantW, 0);
lcid = pLocaleNameToLCID(L"zh-Hant", 0);
ok(lcid == MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG), SORT_DEFAULT),
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(zhHantW), lcid);
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(L"zh-Hant"), lcid);
ret = pLCIDToLocaleName(lcid, buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhHantW), ret);
ok(!lstrcmpW(zhhkW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhHantW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-Hant"), ret);
ok(!lstrcmpW(L"zh-HK", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-Hant"), wine_dbgstr_w(buffer));
/* check that 0x7c04 also works and is mapped to zh-HK */
ret = pLCIDToLocaleName(MAKELANGID(LANG_CHINESE_TRADITIONAL, SUBLANG_CHINESE_TRADITIONAL),
buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhHantW), ret);
ok(!lstrcmpW(zhhkW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhHantW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-Hant"), ret);
ok(!lstrcmpW(L"zh-HK", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-Hant"), wine_dbgstr_w(buffer));
/* zh-hant */
lcid = pLocaleNameToLCID(zhhantW, 0);
lcid = pLocaleNameToLCID(L"zh-hant", 0);
ok(lcid == MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG), SORT_DEFAULT),
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(zhhantW), lcid);
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(L"zh-hant"), lcid);
ret = pLCIDToLocaleName(lcid, buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhhantW), ret);
ok(!lstrcmpW(zhhkW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhhantW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-hant"), ret);
ok(!lstrcmpW(L"zh-HK", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-hant"), wine_dbgstr_w(buffer));
/* zh-Hans has LCID 0x0004, but LocaleNameToLCID actually returns 0x0804, which is the LCID of zh-CN */
lcid = pLocaleNameToLCID(zhHansW, 0);
lcid = pLocaleNameToLCID(L"zh-Hans", 0);
/* check that LocaleNameToLCID actually returns 0x0804 */
ok(lcid == MAKELCID(MAKELANGID(LANG_CHINESE_SIMPLIFIED, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT),
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(zhHansW), lcid);
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(L"zh-Hans"), lcid);
ret = pLCIDToLocaleName(lcid, buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhHansW), ret);
ok(!lstrcmpW(zhcnW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhHansW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-Hans"), ret);
ok(!lstrcmpW(L"zh-CN", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-Hans"), wine_dbgstr_w(buffer));
/* check that 0x0004 also works and is mapped to zh-CN */
ret = pLCIDToLocaleName(MAKELANGID(LANG_CHINESE, SUBLANG_NEUTRAL), buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhHansW), ret);
ok(!lstrcmpW(zhcnW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhHansW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-Hans"), ret);
ok(!lstrcmpW(L"zh-CN", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-Hans"), wine_dbgstr_w(buffer));
/* zh-hans */
lcid = pLocaleNameToLCID(zhhansW, 0);
lcid = pLocaleNameToLCID(L"zh-hans", 0);
ok(lcid == MAKELCID(MAKELANGID(LANG_CHINESE_SIMPLIFIED, SUBLANG_CHINESE_SIMPLIFIED), SORT_DEFAULT),
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(zhhansW), lcid);
"%s: got wrong lcid 0x%04lx\n", wine_dbgstr_w(L"zh-hans"), lcid);
ret = pLCIDToLocaleName(lcid, buffer, ARRAY_SIZE(buffer), 0);
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(zhhansW), ret);
ok(!lstrcmpW(zhcnW, buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(zhhansW), wine_dbgstr_w(buffer));
ok(ret > 0, "%s: got %d\n", wine_dbgstr_w(L"zh-hans"), ret);
ok(!lstrcmpW(L"zh-CN", buffer), "%s: got wrong locale name %s\n",
wine_dbgstr_w(L"zh-hans"), wine_dbgstr_w(buffer));
}
if (pRtlLocaleNameToLcid)
@ -2817,16 +2808,31 @@ static void test_LocaleNameToLCID(void)
ok( lcid == MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), "got %08lx\n", lcid );
lcid = 0;
status = pRtlLocaleNameToLcid( esesW, &lcid, 0 );
status = pRtlLocaleNameToLcid( L"es-es", &lcid, 0 );
ok( !status, "failed error %lx\n", status );
ok( lcid == MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH_MODERN), "got %08lx\n", lcid );
lcid = 0;
status = pRtlLocaleNameToLcid( enW, &lcid, 0 );
status = pRtlLocaleNameToLcid( L"de-DE_phoneb", &lcid, 0 );
ok( !status, "failed error %lx\n", status );
ok( lcid == 0x00010407, "got %08lx\n", lcid );
lcid = 0;
status = pRtlLocaleNameToLcid( L"DE_de-PHONEB", &lcid, 0 );
ok( !status || broken( status == STATUS_INVALID_PARAMETER_1 ), "failed error %lx\n", status );
if (!status) ok( lcid == 0x00010407, "got %08lx\n", lcid );
lcid = 0xdeadbeef;
status = pRtlLocaleNameToLcid( L"de+de+phoneb", &lcid, 0 );
ok( status == STATUS_INVALID_PARAMETER_1, "failed error %lx\n", status );
ok( lcid == 0xdeadbeef, "got %08lx\n", lcid );
lcid = 0;
status = pRtlLocaleNameToLcid( L"en", &lcid, 0 );
ok( status == STATUS_INVALID_PARAMETER_1, "wrong error %lx\n", status );
status = pRtlLocaleNameToLcid( enW, &lcid, 1 );
status = pRtlLocaleNameToLcid( L"en", &lcid, 1 );
ok( status == STATUS_INVALID_PARAMETER_1, "wrong error %lx\n", status );
status = pRtlLocaleNameToLcid( enW, &lcid, 2 );
status = pRtlLocaleNameToLcid( L"en", &lcid, 2 );
ok( !status, "failed error %lx\n", status );
ok( lcid == MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL), "got %08lx\n", lcid );
status = pRtlLocaleNameToLcid( L"en-RR", &lcid, 2 );

View File

@ -238,6 +238,44 @@ invalid:
}
static int compare_locale_names( const WCHAR *n1, const WCHAR *n2 )
{
for (;;)
{
WCHAR ch1 = casemap_ascii( *n1++ );
WCHAR ch2 = casemap_ascii( *n2++ );
if (ch1 == '_') ch1 = '-';
if (ch2 == '_') ch2 = '-';
if (!ch1 || ch1 != ch2) return ch1 - ch2;
}
}
static const NLS_LOCALE_LCNAME_INDEX *find_lcname_entry( const WCHAR *name )
{
int min = 0, max = locale_table->nb_lcnames - 1;
if (!name) return NULL;
while (min <= max)
{
int res, pos = (min + max) / 2;
const WCHAR *str = locale_strings + lcnames_index[pos].name;
res = compare_locale_names( name, str + 1 );
if (res < 0) max = pos - 1;
else if (res > 0) min = pos + 1;
else return &lcnames_index[pos];
}
return NULL;
}
static const NLS_LOCALE_DATA *get_locale_data( UINT idx )
{
ULONG offset = locale_table->locales_offset + idx * locale_table->locale_size;
return (const NLS_LOCALE_DATA *)((const char *)locale_table + offset);
}
void locale_init(void)
{
LARGE_INTEGER unused;
@ -1191,110 +1229,12 @@ WCHAR __cdecl towupper( WCHAR ch )
*/
NTSTATUS WINAPI RtlLocaleNameToLcid( const WCHAR *name, LCID *lcid, ULONG flags )
{
/* locale name format is: lang[-script][-country][_modifier] */
const NLS_LOCALE_LCNAME_INDEX *entry = find_lcname_entry( name );
const IMAGE_RESOURCE_DIRECTORY *resdir;
const IMAGE_RESOURCE_DIRECTORY_ENTRY *et;
LDR_RESOURCE_INFO info;
WCHAR buf[LOCALE_NAME_MAX_LENGTH];
WCHAR lang[LOCALE_NAME_MAX_LENGTH]; /* language ("en") (note: buffer contains the other strings too) */
WCHAR *country = NULL; /* country ("US") */
WCHAR *script = NULL; /* script ("Latn") */
WCHAR *p;
int i;
if (!name) return STATUS_INVALID_PARAMETER_1;
if (!name[0])
{
*lcid = LANG_INVARIANT;
goto found;
}
if (wcslen( name ) >= LOCALE_NAME_MAX_LENGTH) return STATUS_INVALID_PARAMETER_1;
wcscpy( lang, name );
if ((p = wcspbrk( lang, L"-_" )) && *p == '-')
{
*p++ = 0;
country = p;
if ((p = wcspbrk( p, L"-_" )) && *p == '-')
{
*p++ = 0;
script = country;
country = p;
p = wcspbrk( p, L"-_" );
}
if (p) *p = 0; /* FIXME: modifier is ignored */
/* second value can be script or country, check length to resolve the ambiguity */
if (!script && wcslen( country ) == 4)
{
script = country;
country = NULL;
}
}
info.Type = 6; /* RT_STRING */
info.Name = (LOCALE_SNAME >> 4) + 1;
if (LdrFindResourceDirectory_U( kernel32_handle, &info, 2, &resdir ))
return STATUS_INVALID_PARAMETER_1;
et = (const IMAGE_RESOURCE_DIRECTORY_ENTRY *)(resdir + 1);
for (i = 0; i < resdir->NumberOfNamedEntries + resdir->NumberOfIdEntries; i++)
{
LANGID id = et[i].u.Id;
if (PRIMARYLANGID(id) == LANG_NEUTRAL) continue;
if (!load_string( LOCALE_SNAME, id, buf, ARRAY_SIZE(buf) ) && !wcsicmp( name, buf ))
{
*lcid = MAKELCID( id, SORT_DEFAULT ); /* FIXME: handle sort order */
goto found;
}
if (load_string( LOCALE_SISO639LANGNAME, id, buf, ARRAY_SIZE(buf) ) || wcsicmp( lang, buf ))
continue;
if (script)
{
unsigned int len = wcslen( script );
if (load_string( LOCALE_SSCRIPTS, id, buf, ARRAY_SIZE(buf) )) continue;
p = buf;
while (*p)
{
if (!wcsnicmp( p, script, len ) && (!p[len] || p[len] == ';')) break;
if (!(p = wcschr( p, ';'))) break;
p++;
}
if (!p || !*p) continue;
}
if (!country && (flags & 2))
{
if (!script) id = MAKELANGID( PRIMARYLANGID(id), LANG_NEUTRAL );
switch (id)
{
case MAKELANGID( LANG_CHINESE, SUBLANG_NEUTRAL ):
case MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SINGAPORE ):
*lcid = MAKELCID( 0x7804, SORT_DEFAULT );
break;
case MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL ):
case MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_MACAU ):
case MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_HONGKONG ):
*lcid = MAKELCID( 0x7c04, SORT_DEFAULT );
break;
case MAKELANGID( LANG_SERBIAN, SUBLANG_NEUTRAL ):
*lcid = LANG_SERBIAN_NEUTRAL;
break;
default:
*lcid = MAKELANGID( PRIMARYLANGID(id), SUBLANG_NEUTRAL );
break;
}
goto found;
}
}
return STATUS_INVALID_PARAMETER_1;
found:
if (!entry) return STATUS_INVALID_PARAMETER_1;
/* reject neutral locale unless flag 2 is set */
if (!(flags & 2) && !get_locale_data( entry->idx )->inotneutral) return STATUS_INVALID_PARAMETER_1;
*lcid = entry->id;
TRACE( "%s -> %04x\n", debugstr_w(name), *lcid );
return STATUS_SUCCESS;
}