From: André Hentschel Subject: kernel32: Add LCMapStringEx implementation and test it reusing tests from LCMapStringW (try 2) Message-Id: <4F2EA987.6060707@dawncrow.de> Date: Sun, 05 Feb 2012 17:08:39 +0100 http://bugs.winehq.org/show_bug.cgi?id=29730 --- dlls/kernel32/kernel32.spec | 1 + dlls/kernel32/locale.c | 33 ++++++++ dlls/kernel32/tests/locale.c | 175 ++++++++++++++++++++++++++++++++++++++++++ include/winnls.h | 34 ++++++++ 4 files changed, 243 insertions(+), 0 deletions(-) diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec index 072cfa1..2629469 100644 --- a/dlls/kernel32/kernel32.spec +++ b/dlls/kernel32/kernel32.spec @@ -804,6 +804,7 @@ @ stdcall LCIDToLocaleName(long ptr long long) @ stdcall LCMapStringA(long long str long ptr long) @ stdcall LCMapStringW(long long wstr long ptr long) +@ stdcall LCMapStringEx(long long wstr long ptr long ptr ptr long) @ stdcall LZClose(long) # @ stub LZCloseFile @ stdcall LZCopy(long long) diff --git a/dlls/kernel32/locale.c b/dlls/kernel32/locale.c index 2c3760c..222952b 100644 --- a/dlls/kernel32/locale.c +++ b/dlls/kernel32/locale.c @@ -2680,6 +2680,39 @@ map_string_exit: } /************************************************************************* + * LCMapStringEx (KERNEL32.@) + * + * Map characters in a locale sensitive string. + * + * PARAMS + * name [I] na for the conversion. + * flags [I] Flags controlling the mapping (LCMAP_ constants from "winnls.h"). + * src [I] String to map + * srclen [I] Length of src in chars, or -1 if src is NUL terminated + * dst [O] Destination for mapped string + * dstlen [I] Length of dst in characters + * version [I] reserved, must be NULL + * reserved [I] reserved, must be NULL + * lparam [I] reserved, must be 0 + * + * RETURNS + * Success: The length of the mapped string in dst, including the NUL terminator. + * Failure: 0. Use GetLastError() to determine the cause. + */ +INT WINAPI LCMapStringEx(LPCWSTR name, DWORD flags, LPCWSTR src, INT srclen, LPWSTR dst, INT dstlen, + LPNLSVERSIONINFO version, LPVOID reserved, LPARAM lparam) +{ + LCID lcid; + + if (version) FIXME("unsupported version structure %p\n", version); + if (reserved) FIXME("unsupported reserved pointer %p\n", reserved); + if (lparam) FIXME("unsupported lparam %lx\n", lparam); + + lcid = LocaleNameToLCID(name, 0); + return LCMapStringW(lcid, flags, src, srclen, dst, dstlen); +} + +/************************************************************************* * FoldStringA (KERNEL32.@) * * Map characters in a string. diff --git a/dlls/kernel32/tests/locale.c b/dlls/kernel32/tests/locale.c index f5b77f7..667bf59 100644 --- a/dlls/kernel32/tests/locale.c +++ b/dlls/kernel32/tests/locale.c @@ -74,6 +74,8 @@ static BOOL (WINAPI *pEnumSystemLocalesEx)(LOCALE_ENUMPROCEX, DWORD, LPARAM, LPV static INT (WINAPI *pFoldStringA)(DWORD, LPCSTR, INT, LPSTR, INT); static INT (WINAPI *pFoldStringW)(DWORD, LPCWSTR, INT, LPWSTR, INT); static BOOL (WINAPI *pIsValidLanguageGroup)(LGRPID, DWORD); +static INT (WINAPI *pLCMapStringEx)(LPCWSTR, DWORD, LPCWSTR, INT, LPWSTR, INT, LPNLSVERSIONINFO, LPVOID, LPARAM); +static INT (WINAPI *pLCIDToLocaleName)(LCID, LPWSTR, INT, DWORD); static void InitFunctionPointers(void) { @@ -85,6 +87,8 @@ static void InitFunctionPointers(void) pIsValidLanguageGroup = (void*)GetProcAddress(hKernel32, "IsValidLanguageGroup"); pEnumUILanguagesA = (void*)GetProcAddress(hKernel32, "EnumUILanguagesA"); pEnumSystemLocalesEx = (void*)GetProcAddress(hKernel32, "EnumSystemLocalesEx"); + pLCMapStringEx = (void*)GetProcAddress(hKernel32, "LCMapStringEx"); + pLCIDToLocaleName = (void*)GetProcAddress(hKernel32, "LCIDToLocaleName"); } #define eq(received, expected, label, type) \ @@ -1637,6 +1641,176 @@ static void test_LCMapStringW(void) "unexpected error code %d\n", GetLastError()); } +static void test_LCMapStringEx(void) +{ + int ret, ret2; + WCHAR buf[256], buf2[256]; + WCHAR locale_user_default[LOCALE_NAME_MAX_LENGTH]; + char *p_buf = (char *)buf, *p_buf2 = (char *)buf2; + static const WCHAR upper_case[] = {'\t','J','U','S','T','!',' ','A',',',' ','T','E','S','T',';',' ','S','T','R','I','N','G',' ','1','/','*','+','-','.','\r','\n',0}; + static const WCHAR lower_case[] = {'\t','j','u','s','t','!',' ','a',',',' ','t','e','s','t',';',' ','s','t','r','i','n','g',' ','1','/','*','+','-','.','\r','\n',0}; + static const WCHAR symbols_stripped[] = {'j','u','s','t','a','t','e','s','t','s','t','r','i','n','g','1',0}; + static const WCHAR fooW[] = {'f','o','o',0}; + + if (!pLCMapStringEx) + { + win_skip( "EnumSystemLocalesEx not available\n" ); + return; + } + + if(!pLCIDToLocaleName) + { + win_skip( "LCIDToLocaleName not available\n" ); + return; + } + + ret = pLCIDToLocaleName(LOCALE_USER_DEFAULT, locale_user_default, + sizeof(locale_user_default)/sizeof(WCHAR), 0); + ok(ret, "LCIDToLocaleName failed: %u\n", GetLastError()); + + ret = pLCMapStringEx(locale_user_default, LCMAP_LOWERCASE | LCMAP_UPPERCASE, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + if (broken(ret)) + ok(lstrcmpW(buf, upper_case) == 0, "Expected upper case string\n"); + else + { + ok(!ret, "LCMAP_LOWERCASE and LCMAP_UPPERCASE are mutually exclusive\n"); + ok(GetLastError() == ERROR_INVALID_FLAGS, + "unexpected error code %d\n", GetLastError()); + } + + ret = pLCMapStringEx(locale_user_default, LCMAP_HIRAGANA | LCMAP_KATAKANA, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(!ret, "LCMAP_HIRAGANA and LCMAP_KATAKANA are mutually exclusive\n"); + ok(GetLastError() == ERROR_INVALID_FLAGS, + "unexpected error code %d\n", GetLastError()); + + ret = pLCMapStringEx(locale_user_default, LCMAP_HALFWIDTH | LCMAP_FULLWIDTH, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(!ret, "LCMAP_HALFWIDTH | LCMAP_FULLWIDTH are mutually exclusive\n"); + ok(GetLastError() == ERROR_INVALID_FLAGS, + "unexpected error code %d\n", GetLastError()); + + ret = pLCMapStringEx(locale_user_default, LCMAP_TRADITIONAL_CHINESE | LCMAP_SIMPLIFIED_CHINESE, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(!ret, "LCMAP_TRADITIONAL_CHINESE and LCMAP_SIMPLIFIED_CHINESE are mutually exclusive\n"); + ok(GetLastError() == ERROR_INVALID_FLAGS, + "unexpected error code %d\n", GetLastError()); + + /* SORT_STRINGSORT must be used exclusively with LCMAP_SORTKEY */ + SetLastError(0xdeadbeef); + ret = pLCMapStringEx(locale_user_default, LCMAP_LOWERCASE | SORT_STRINGSORT, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(GetLastError() == ERROR_INVALID_FLAGS, "expected ERROR_INVALID_FLAGS, got %d\n", GetLastError()); + ok(!ret, "SORT_STRINGSORT without LCMAP_SORTKEY must fail\n"); + + /* test LCMAP_LOWERCASE */ + ret = pLCMapStringEx(locale_user_default, LCMAP_LOWERCASE, + upper_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(upper_case) + 1, + "ret %d, error %d, expected value %d\n", + ret, GetLastError(), lstrlenW(upper_case) + 1); + ok(!lstrcmpW(buf, lower_case), "string compare mismatch\n"); + + /* test LCMAP_UPPERCASE */ + ret = pLCMapStringEx(locale_user_default, LCMAP_UPPERCASE, + lower_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(lower_case) + 1, + "ret %d, error %d, expected value %d\n", + ret, GetLastError(), lstrlenW(lower_case) + 1); + ok(!lstrcmpW(buf, upper_case), "string compare mismatch\n"); + + /* test buffer overflow */ + SetLastError(0xdeadbeef); + ret = pLCMapStringEx(locale_user_default, LCMAP_UPPERCASE, + lower_case, -1, buf, 4, NULL, NULL, 0); + ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, + "should return 0 and ERROR_INSUFFICIENT_BUFFER, got %d\n", ret); + + /* LCMAP_UPPERCASE or LCMAP_LOWERCASE should accept src == dst */ + lstrcpyW(buf, lower_case); + ret = pLCMapStringEx(locale_user_default, LCMAP_UPPERCASE, + buf, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(lower_case) + 1, + "ret %d, error %d, expected value %d\n", + ret, GetLastError(), lstrlenW(lower_case) + 1); + ok(!lstrcmpW(buf, upper_case), "string compare mismatch\n"); + + lstrcpyW(buf, upper_case); + ret = pLCMapStringEx(locale_user_default, LCMAP_LOWERCASE, + buf, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(upper_case) + 1, + "ret %d, error %d, expected value %d\n", + ret, GetLastError(), lstrlenW(lower_case) + 1); + ok(!lstrcmpW(buf, lower_case), "string compare mismatch\n"); + + /* otherwise src == dst should fail */ + SetLastError(0xdeadbeef); + ret = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY | LCMAP_UPPERCASE, + buf, 10, buf, sizeof(buf), NULL, NULL, 0); + ok(GetLastError() == ERROR_INVALID_FLAGS /* NT */ || + GetLastError() == ERROR_INVALID_PARAMETER /* Win9x */, + "unexpected error code %d\n", GetLastError()); + ok(!ret, "src == dst without LCMAP_UPPERCASE or LCMAP_LOWERCASE must fail\n"); + + /* test whether '\0' is always appended */ + ret = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY, + upper_case, -1, buf, sizeof(buf), NULL, NULL, 0); + ok(ret, "pLCMapStringEx must succeed\n"); + ret2 = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY, + upper_case, lstrlenW(upper_case), buf2, sizeof(buf2), NULL, NULL, 0); + ok(ret, "pLCMapStringEx must succeed\n"); + ok(ret == ret2, "lengths of sort keys must be equal\n"); + ok(!lstrcmpA(p_buf, p_buf2), "sort keys must be equal\n"); + + /* test LCMAP_SORTKEY | NORM_IGNORECASE */ + ret = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY | NORM_IGNORECASE, + upper_case, -1, buf, sizeof(buf), NULL, NULL, 0); + ok(ret, "pLCMapStringEx must succeed\n"); + ret2 = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY, + lower_case, -1, buf2, sizeof(buf2), NULL, NULL, 0); + ok(ret2, "pLCMapStringEx must succeed\n"); + ok(ret == ret2, "lengths of sort keys must be equal\n"); + ok(!lstrcmpA(p_buf, p_buf2), "sort keys must be equal\n"); + + /* Don't test LCMAP_SORTKEY | NORM_IGNORENONSPACE, produces different + results from plain LCMAP_SORTKEY on Vista */ + + /* test LCMAP_SORTKEY | NORM_IGNORESYMBOLS */ + ret = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY | NORM_IGNORESYMBOLS, + lower_case, -1, buf, sizeof(buf), NULL, NULL, 0); + ok(ret, "pLCMapStringEx must succeed\n"); + ret2 = pLCMapStringEx(locale_user_default, LCMAP_SORTKEY, + symbols_stripped, -1, buf2, sizeof(buf2), NULL, NULL, 0); + ok(ret2, "pLCMapStringEx must succeed\n"); + ok(ret == ret2, "lengths of sort keys must be equal\n"); + ok(!lstrcmpA(p_buf, p_buf2), "sort keys must be equal\n"); + + /* test NORM_IGNORENONSPACE */ + lstrcpyW(buf, fooW); + ret = pLCMapStringEx(locale_user_default, NORM_IGNORENONSPACE, + lower_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(lower_case) + 1, "pLCMapStringEx should return %d, ret = %d\n", + lstrlenW(lower_case) + 1, ret); + ok(!lstrcmpW(buf, lower_case), "string comparison mismatch\n"); + + /* test NORM_IGNORESYMBOLS */ + lstrcpyW(buf, fooW); + ret = pLCMapStringEx(locale_user_default, NORM_IGNORESYMBOLS, + lower_case, -1, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(ret == lstrlenW(symbols_stripped) + 1, "pLCMapStringEx should return %d, ret = %d\n", + lstrlenW(symbols_stripped) + 1, ret); + ok(!lstrcmpW(buf, symbols_stripped), "string comparison mismatch\n"); + + /* test srclen = 0 */ + SetLastError(0xdeadbeef); + ret = pLCMapStringEx(locale_user_default, 0, + upper_case, 0, buf, sizeof(buf)/sizeof(WCHAR), NULL, NULL, 0); + ok(!ret, "pLCMapStringEx should fail with srclen = 0\n"); + ok(GetLastError() == ERROR_INVALID_PARAMETER, + "unexpected error code %d\n", GetLastError()); +} + /* this requires collation table patch to make it MS compatible */ static const char * const strings_sorted[] = { @@ -2782,6 +2956,7 @@ START_TEST(locale) test_CompareStringA(); test_LCMapStringA(); test_LCMapStringW(); + test_LCMapStringEx(); test_FoldStringA(); test_FoldStringW(); test_ConvertDefaultLocale(); diff --git a/include/winnls.h b/include/winnls.h index de6f875..112ad3d 100644 --- a/include/winnls.h +++ b/include/winnls.h @@ -321,6 +321,26 @@ extern "C" { #define LOCALE_SSORTNAME 0x1013 #define LOCALE_IDIGITSUBSTITUTION 0x1014 +/* Locale name special values */ +#if defined(__GNUC__) +# define LOCALE_NAME_INVARIANT (const WCHAR []){ 0 } +#elif defined(_MSC_VER) +# define LOCALE_NAME_INVARIANT L"" +#else +static const WCHAR LOCALE_NAME_INVARIANT[] = { 0 }; +#endif + +#if defined(__GNUC__) +# define LOCALE_NAME_SYSTEM_DEFAULT (const WCHAR []){'!','s','y','s','-','d','e','f','a','u','l','t','-','l','o','c','a','l','e',0}; +#elif defined(_MSC_VER) +# define LOCALE_NAME_SYSTEM_DEFAULT L"!sys-default-locale" +#else +static const WCHAR LOCALE_NAME_SYSTEM_DEFAULT[] = {'!','s','y','s','-','d','e','f','a','u','l','t','-','l','o','c','a','l','e',0}; +#endif + +#define LOCALE_NAME_MAX_LENGTH 85 +#define LOCALE_NAME_USER_DEFAULT NULL + #define LOCALE_IDEFAULTUNIXCODEPAGE 0x1030 /* Wine extension */ #define NORM_IGNORECASE 0x00001 @@ -648,6 +668,19 @@ typedef struct _currencyfmtW DECL_WINELIB_TYPE_AW(CURRENCYFMT) DECL_WINELIB_TYPE_AW(LPCURRENCYFMT) +typedef struct _nlsversioninfo { + DWORD dwNLSVersionInfoSize; + DWORD dwNLSVersion; + DWORD dwDefinedVersion; +} NLSVERSIONINFO, *LPNLSVERSIONINFO; + +typedef struct _nlsversioninfoex { + DWORD dwNLSVersionInfoSize; + DWORD dwNLSVersion; + DWORD dwDefinedVersion; + DWORD dwEffectiveId; + GUID guidCustomVersion; +} NLSVERSIONINFOEX, *LPNLSVERSIONINFOEX; /* Define a bunch of callback types */ @@ -830,6 +863,7 @@ WINBASEAPI INT WINAPI LCIDToLocaleName(LCID,LPWSTR,INT,DWORD); WINBASEAPI INT WINAPI LCMapStringA(LCID,DWORD,LPCSTR,INT,LPSTR,INT); WINBASEAPI INT WINAPI LCMapStringW(LCID,DWORD,LPCWSTR,INT,LPWSTR,INT); #define LCMapString WINELIB_NAME_AW(LCMapString) +WINBASEAPI INT WINAPI LCMapStringEx(LPCWSTR,DWORD,LPCWSTR,INT,LPWSTR,INT,LPNLSVERSIONINFO,LPVOID,LPARAM); WINBASEAPI LCID WINAPI LocaleNameToLCID(LPCWSTR,DWORD); WINBASEAPI INT WINAPI MultiByteToWideChar(UINT,DWORD,LPCSTR,INT,LPWSTR,INT); WINNORMALIZEAPI INT WINAPI NormalizeString(NORM_FORM,LPCWSTR,INT,LPWSTR,INT); -- Best Regards, André Hentschel