xref: /reactos/dll/win32/comctl32/string.c (revision 84ccccab)
1 /*
2  * String manipulation functions
3  *
4  * Copyright 1998 Eric Kohl
5  *           1998 Juergen Schmied <j.schmied@metronet.de>
6  *           2000 Eric Kohl for CodeWeavers
7  * Copyright 2002 Jon Griffiths
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  *
23  */
24 
25 #include "comctl32.h"
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
28 
29 /*************************************************************************
30  * COMCTL32_ChrCmpHelperA
31  *
32  * Internal helper for ChrCmpA/COMCTL32_ChrCmpIA.
33  *
34  * NOTES
35  *  Both this function and its Unicode counterpart are very inefficient. To
36  *  fix this, CompareString must be completely implemented and optimised
37  *  first. Then the core character test can be taken out of that function and
38  *  placed here, so that it need never be called at all. Until then, do not
39  *  attempt to optimise this code unless you are willing to test that it
40  *  still performs correctly.
41  */
42 static BOOL COMCTL32_ChrCmpHelperA(WORD ch1, WORD ch2, DWORD dwFlags)
43 {
44   char str1[3], str2[3];
45 
46   str1[0] = LOBYTE(ch1);
47   if (IsDBCSLeadByte(str1[0]))
48   {
49     str1[1] = HIBYTE(ch1);
50     str1[2] = '\0';
51   }
52   else
53     str1[1] = '\0';
54 
55   str2[0] = LOBYTE(ch2);
56   if (IsDBCSLeadByte(str2[0]))
57   {
58     str2[1] = HIBYTE(ch2);
59     str2[2] = '\0';
60   }
61   else
62     str2[1] = '\0';
63 
64   return CompareStringA(GetThreadLocale(), dwFlags, str1, -1, str2, -1) - CSTR_EQUAL;
65 }
66 
67 /*************************************************************************
68  * COMCTL32_ChrCmpA (internal)
69  *
70  * Internal helper function.
71  */
72 static BOOL COMCTL32_ChrCmpA(WORD ch1, WORD ch2)
73 {
74   return COMCTL32_ChrCmpHelperA(ch1, ch2, 0);
75 }
76 
77 /*************************************************************************
78  * COMCTL32_ChrCmpIA	(internal)
79  *
80  * Compare two characters, ignoring case.
81  *
82  * PARAMS
83  *  ch1 [I] First character to compare
84  *  ch2 [I] Second character to compare
85  *
86  * RETURNS
87  *  FALSE, if the characters are equal.
88  *  Non-zero otherwise.
89  */
90 static BOOL COMCTL32_ChrCmpIA(WORD ch1, WORD ch2)
91 {
92   TRACE("(%d,%d)\n", ch1, ch2);
93 
94   return COMCTL32_ChrCmpHelperA(ch1, ch2, NORM_IGNORECASE);
95 }
96 
97 /*************************************************************************
98  * COMCTL32_ChrCmpIW
99  *
100  * Internal helper function.
101  */
102 static inline BOOL COMCTL32_ChrCmpIW(WCHAR ch1, WCHAR ch2)
103 {
104   return CompareStringW(GetThreadLocale(), NORM_IGNORECASE, &ch1, 1, &ch2, 1) - CSTR_EQUAL;
105 }
106 
107 /**************************************************************************
108  * Str_GetPtrA [COMCTL32.233]
109  *
110  * Copies a string into a destination buffer.
111  *
112  * PARAMS
113  *     lpSrc   [I] Source string
114  *     lpDest  [O] Destination buffer
115  *     nMaxLen [I] Size of buffer in characters
116  *
117  * RETURNS
118  *     The number of characters copied.
119  */
120 INT WINAPI Str_GetPtrA (LPCSTR lpSrc, LPSTR lpDest, INT nMaxLen)
121 {
122     INT len;
123 
124     TRACE("(%p %p %d)\n", lpSrc, lpDest, nMaxLen);
125 
126     if ((!lpDest || nMaxLen == 0) && lpSrc)
127         return (strlen(lpSrc) + 1);
128 
129     if (nMaxLen == 0)
130         return 0;
131 
132     if (lpSrc == NULL) {
133         lpDest[0] = '\0';
134         return 0;
135     }
136 
137     len = strlen(lpSrc) + 1;
138     if (len >= nMaxLen)
139         len = nMaxLen;
140 
141     RtlMoveMemory (lpDest, lpSrc, len - 1);
142     lpDest[len - 1] = '\0';
143 
144     return len;
145 }
146 
147 /**************************************************************************
148  * Str_SetPtrA [COMCTL32.234]
149  *
150  * Makes a copy of a string, allocating memory if necessary.
151  *
152  * PARAMS
153  *     lppDest [O] Pointer to destination string
154  *     lpSrc   [I] Source string
155  *
156  * RETURNS
157  *     Success: TRUE
158  *     Failure: FALSE
159  *
160  * NOTES
161  *     Set lpSrc to NULL to free the memory allocated by a previous call
162  *     to this function.
163  */
164 BOOL WINAPI Str_SetPtrA (LPSTR *lppDest, LPCSTR lpSrc)
165 {
166     TRACE("(%p %p)\n", lppDest, lpSrc);
167 
168     if (lpSrc) {
169         LPSTR ptr = ReAlloc (*lppDest, strlen (lpSrc) + 1);
170         if (!ptr)
171             return FALSE;
172         strcpy (ptr, lpSrc);
173         *lppDest = ptr;
174     }
175     else {
176         Free (*lppDest);
177         *lppDest = NULL;
178     }
179 
180     return TRUE;
181 }
182 
183 /**************************************************************************
184  * Str_GetPtrW [COMCTL32.235]
185  *
186  * See Str_GetPtrA.
187  */
188 INT WINAPI Str_GetPtrW (LPCWSTR lpSrc, LPWSTR lpDest, INT nMaxLen)
189 {
190     INT len;
191 
192     TRACE("(%p %p %d)\n", lpSrc, lpDest, nMaxLen);
193 
194     if (!lpDest && lpSrc)
195         return strlenW (lpSrc);
196 
197     if (nMaxLen == 0)
198         return 0;
199 
200     if (lpSrc == NULL) {
201         lpDest[0] = '\0';
202         return 0;
203     }
204 
205     len = strlenW (lpSrc);
206     if (len >= nMaxLen)
207         len = nMaxLen - 1;
208 
209     RtlMoveMemory (lpDest, lpSrc, len*sizeof(WCHAR));
210     lpDest[len] = '\0';
211 
212     return len;
213 }
214 
215 /**************************************************************************
216  * Str_SetPtrW [COMCTL32.236]
217  *
218  * See Str_SetPtrA.
219  */
220 BOOL WINAPI Str_SetPtrW (LPWSTR *lppDest, LPCWSTR lpSrc)
221 {
222     TRACE("(%p %s)\n", lppDest, debugstr_w(lpSrc));
223 
224     if (lpSrc) {
225         INT len = strlenW (lpSrc) + 1;
226         LPWSTR ptr = ReAlloc (*lppDest, len * sizeof(WCHAR));
227         if (!ptr)
228             return FALSE;
229         strcpyW (ptr, lpSrc);
230         *lppDest = ptr;
231     }
232     else {
233         Free (*lppDest);
234         *lppDest = NULL;
235     }
236 
237     return TRUE;
238 }
239 
240 /**************************************************************************
241  * StrChrA [COMCTL32.350]
242  *
243  * Find a given character in a string.
244  *
245  * PARAMS
246  *  lpszStr [I] String to search in.
247  *  ch      [I] Character to search for.
248  *
249  * RETURNS
250  *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
251  *           not found.
252  *  Failure: NULL, if any arguments are invalid.
253  */
254 LPSTR WINAPI StrChrA(LPCSTR lpszStr, WORD ch)
255 {
256   TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);
257 
258   if (lpszStr)
259   {
260     while (*lpszStr)
261     {
262       if (!COMCTL32_ChrCmpA(*lpszStr, ch))
263         return (LPSTR)lpszStr;
264       lpszStr = CharNextA(lpszStr);
265     }
266   }
267   return NULL;
268 }
269 
270 /**************************************************************************
271  * StrCmpNIA [COMCTL32.353]
272  *
273  * Compare two strings, up to a maximum length, ignoring case.
274  *
275  * PARAMS
276  *  lpszStr  [I] First string to compare
277  *  lpszComp [I] Second string to compare
278  *  iLen     [I] Number of chars to compare
279  *
280  * RETURNS
281  *  An integer less than, equal to or greater than 0, indicating that
282  *  lpszStr is less than, the same, or greater than lpszComp.
283  */
284 INT WINAPI StrCmpNIA(LPCSTR lpszStr, LPCSTR lpszComp, INT iLen)
285 {
286   TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
287   return CompareStringA(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
288 }
289 
290 /*************************************************************************
291  * StrCmpNIW	[COMCTL32.361]
292  *
293  * See StrCmpNIA.
294  */
295 INT WINAPI StrCmpNIW(LPCWSTR lpszStr, LPCWSTR lpszComp, INT iLen)
296 {
297   TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);
298   return CompareStringW(GetThreadLocale(), NORM_IGNORECASE, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
299 }
300 
301 /*************************************************************************
302  * COMCTL32_StrStrHelperA
303  *
304  * Internal implementation of StrStrA/StrStrIA
305  */
306 static LPSTR COMCTL32_StrStrHelperA(LPCSTR lpszStr, LPCSTR lpszSearch,
307                                     INT (WINAPI *pStrCmpFn)(LPCSTR,LPCSTR,INT))
308 {
309   size_t iLen;
310   LPCSTR end;
311 
312   if (!lpszStr || !lpszSearch || !*lpszSearch)
313     return NULL;
314 
315   iLen = strlen(lpszSearch);
316   end = lpszStr + strlen(lpszStr);
317 
318   while (lpszStr + iLen <= end)
319   {
320     if (!pStrCmpFn(lpszStr, lpszSearch, iLen))
321       return (LPSTR)lpszStr;
322     lpszStr = CharNextA(lpszStr);
323   }
324   return NULL;
325 }
326 
327 /**************************************************************************
328  * StrStrIA [COMCTL32.355]
329  *
330  * Find a substring within a string, ignoring case.
331  *
332  * PARAMS
333  *  lpszStr    [I] String to search in
334  *  lpszSearch [I] String to look for
335  *
336  * RETURNS
337  *  The start of lpszSearch within lpszStr, or NULL if not found.
338  */
339 LPSTR WINAPI StrStrIA(LPCSTR lpszStr, LPCSTR lpszSearch)
340 {
341   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
342 
343   return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, StrCmpNIA);
344 }
345 
346 /**************************************************************************
347  * StrToIntA [COMCTL32.357]
348  *
349  * Read a signed integer from a string.
350  *
351  * PARAMS
352  *  lpszStr [I] String to read integer from
353  *
354  * RETURNS
355  *   The signed integer value represented by the string, or 0 if no integer is
356  *   present.
357  */
358 INT WINAPI StrToIntA (LPCSTR lpszStr)
359 {
360     return atoi(lpszStr);
361 }
362 
363 /**************************************************************************
364  * StrStrIW [COMCTL32.363]
365  *
366  * See StrStrIA.
367  */
368 LPWSTR WINAPI StrStrIW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
369 {
370   int iLen;
371   LPCWSTR end;
372 
373   TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));
374 
375   if (!lpszStr || !lpszSearch || !*lpszSearch)
376     return NULL;
377 
378   iLen = strlenW(lpszSearch);
379   end = lpszStr + strlenW(lpszStr);
380 
381   while (lpszStr + iLen <= end)
382   {
383     if (!StrCmpNIW(lpszStr, lpszSearch, iLen))
384       return (LPWSTR)lpszStr;
385     lpszStr++;
386   }
387   return NULL;
388 }
389 
390 /**************************************************************************
391  * StrToIntW [COMCTL32.365]
392  *
393  * See StrToIntA.
394  */
395 INT WINAPI StrToIntW (LPCWSTR lpString)
396 {
397     return atoiW(lpString);
398 }
399 
400 /*************************************************************************
401  * COMCTL32_StrSpnHelperA (internal)
402  *
403  * Internal implementation of StrSpnA/StrCSpnA/StrCSpnIA
404  */
405 static int COMCTL32_StrSpnHelperA(LPCSTR lpszStr, LPCSTR lpszMatch,
406                                   LPSTR (WINAPI *pStrChrFn)(LPCSTR,WORD),
407                                   BOOL bInvert)
408 {
409   LPCSTR lpszRead = lpszStr;
410   if (lpszStr && *lpszStr && lpszMatch)
411   {
412     while (*lpszRead)
413     {
414       LPCSTR lpszTest = pStrChrFn(lpszMatch, *lpszRead);
415 
416       if (!bInvert && !lpszTest)
417         break;
418       if (bInvert && lpszTest)
419         break;
420       lpszRead = CharNextA(lpszRead);
421     };
422   }
423   return lpszRead - lpszStr;
424 }
425 
426 /**************************************************************************
427  * StrCSpnA [COMCTL32.356]
428  *
429  * Find the length of the start of a string that does not contain certain
430  * characters.
431  *
432  * PARAMS
433  *  lpszStr   [I] String to search
434  *  lpszMatch [I] Characters that cannot be in the substring
435  *
436  * RETURNS
437  *  The length of the part of lpszStr containing only chars not in lpszMatch,
438  *  or 0 if any parameter is invalid.
439  */
440 int WINAPI StrCSpnA(LPCSTR lpszStr, LPCSTR lpszMatch)
441 {
442   TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));
443 
444   return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrA, TRUE);
445 }
446 
447 /**************************************************************************
448  * StrChrW [COMCTL32.358]
449  *
450  * See StrChrA.
451  */
452 LPWSTR WINAPI StrChrW(LPCWSTR lpszStr, WCHAR ch)
453 {
454   LPWSTR lpszRet = NULL;
455 
456   TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);
457 
458   if (lpszStr)
459     lpszRet = strchrW(lpszStr, ch);
460   return lpszRet;
461 }
462 
463 /**************************************************************************
464  * StrCmpNA [COMCTL32.352]
465  *
466  * Compare two strings, up to a maximum length.
467  *
468  * PARAMS
469  *  lpszStr  [I] First string to compare
470  *  lpszComp [I] Second string to compare
471  *  iLen     [I] Number of chars to compare
472  *
473  * RETURNS
474  *  An integer less than, equal to or greater than 0, indicating that
475  *  lpszStr is less than, the same, or greater than lpszComp.
476  */
477 INT WINAPI StrCmpNA(LPCSTR lpszStr, LPCSTR lpszComp, INT iLen)
478 {
479   TRACE("(%s,%s,%i)\n", debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
480   return CompareStringA(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
481 }
482 
483 /**************************************************************************
484  * StrCmpNW [COMCTL32.360]
485  *
486  * See StrCmpNA.
487  */
488 INT WINAPI StrCmpNW(LPCWSTR lpszStr, LPCWSTR lpszComp, INT iLen)
489 {
490   TRACE("(%s,%s,%i)\n", debugstr_w(lpszStr), debugstr_w(lpszComp), iLen);
491   return CompareStringW(GetThreadLocale(), 0, lpszStr, iLen, lpszComp, iLen) - CSTR_EQUAL;
492 }
493 
494 /**************************************************************************
495  * StrRChrA [COMCTL32.351]
496  *
497  * Find the last occurrence of a character in string.
498  *
499  * PARAMS
500  *  lpszStr [I] String to search in
501  *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
502  *  ch      [I] Character to search for.
503  *
504  * RETURNS
505  *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
506  *           or NULL if not found.
507  *  Failure: NULL, if any arguments are invalid.
508  */
509 LPSTR WINAPI StrRChrA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
510 {
511   LPCSTR lpszRet = NULL;
512 
513   TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);
514 
515   if (lpszStr)
516   {
517     WORD ch2;
518 
519     if (!lpszEnd)
520       lpszEnd = lpszStr + lstrlenA(lpszStr);
521 
522     while (*lpszStr && lpszStr <= lpszEnd)
523     {
524       ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;
525 
526       if (!COMCTL32_ChrCmpA(ch, ch2))
527         lpszRet = lpszStr;
528       lpszStr = CharNextA(lpszStr);
529     }
530   }
531   return (LPSTR)lpszRet;
532 }
533 
534 
535 /**************************************************************************
536  * StrRChrW [COMCTL32.359]
537  *
538  * See StrRChrA.
539  */
540 LPWSTR WINAPI StrRChrW(LPCWSTR str, LPCWSTR end, WORD ch)
541 {
542     WCHAR *ret = NULL;
543 
544     if (!str) return NULL;
545     if (!end) end = str + strlenW(str);
546     while (str < end)
547     {
548         if (*str == ch) ret = (WCHAR *)str;
549         str++;
550     }
551     return ret;
552 }
553 
554 /**************************************************************************
555  * StrStrA [COMCTL32.354]
556  *
557  * Find a substring within a string.
558  *
559  * PARAMS
560  *  lpszStr    [I] String to search in
561  *  lpszSearch [I] String to look for
562  *
563  * RETURNS
564  *  The start of lpszSearch within lpszStr, or NULL if not found.
565  */
566 LPSTR WINAPI StrStrA(LPCSTR lpszStr, LPCSTR lpszSearch)
567 {
568   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
569 
570   return COMCTL32_StrStrHelperA(lpszStr, lpszSearch, StrCmpNA);
571 }
572 
573 /**************************************************************************
574  * StrStrW [COMCTL32.362]
575  *
576  * See StrStrA.
577  */
578 LPWSTR WINAPI StrStrW(LPCWSTR lpszStr, LPCWSTR lpszSearch)
579 {
580     if (!lpszStr || !lpszSearch) return NULL;
581     return strstrW( lpszStr, lpszSearch );
582 }
583 
584 /*************************************************************************
585  * StrChrIA	[COMCTL32.366]
586  *
587  * Find a given character in a string, ignoring case.
588  *
589  * PARAMS
590  *  lpszStr [I] String to search in.
591  *  ch      [I] Character to search for.
592  *
593  * RETURNS
594  *  Success: A pointer to the first occurrence of ch in lpszStr, or NULL if
595  *           not found.
596  *  Failure: NULL, if any arguments are invalid.
597  */
598 LPSTR WINAPI StrChrIA(LPCSTR lpszStr, WORD ch)
599 {
600   TRACE("(%s,%i)\n", debugstr_a(lpszStr), ch);
601 
602   if (lpszStr)
603   {
604     while (*lpszStr)
605     {
606       if (!COMCTL32_ChrCmpIA(*lpszStr, ch))
607         return (LPSTR)lpszStr;
608       lpszStr = CharNextA(lpszStr);
609     }
610   }
611   return NULL;
612 }
613 
614 /*************************************************************************
615  * StrChrIW	[COMCTL32.367]
616  *
617  * See StrChrA.
618  */
619 LPWSTR WINAPI StrChrIW(LPCWSTR lpszStr, WCHAR ch)
620 {
621   TRACE("(%s,%i)\n", debugstr_w(lpszStr), ch);
622 
623   if (lpszStr)
624   {
625     ch = toupperW(ch);
626     while (*lpszStr)
627     {
628       if (toupperW(*lpszStr) == ch)
629         return (LPWSTR)lpszStr;
630       lpszStr++;
631     }
632     lpszStr = NULL;
633   }
634   return (LPWSTR)lpszStr;
635 }
636 
637 /*************************************************************************
638  * StrRStrIA	[COMCTL32.372]
639  *
640  * Find the last occurrence of a substring within a string.
641  *
642  * PARAMS
643  *  lpszStr    [I] String to search in
644  *  lpszEnd    [I] End of lpszStr
645  *  lpszSearch [I] String to look for
646  *
647  * RETURNS
648  *  The last occurrence lpszSearch within lpszStr, or NULL if not found.
649  */
650 LPSTR WINAPI StrRStrIA(LPCSTR lpszStr, LPCSTR lpszEnd, LPCSTR lpszSearch)
651 {
652   LPSTR lpszRet = NULL;
653   WORD ch1, ch2;
654   INT iLen;
655 
656   TRACE("(%s,%s)\n", debugstr_a(lpszStr), debugstr_a(lpszSearch));
657 
658   if (!lpszStr || !lpszSearch || !*lpszSearch)
659     return NULL;
660 
661   if (IsDBCSLeadByte(*lpszSearch))
662     ch1 = *lpszSearch << 8 | (UCHAR)lpszSearch[1];
663   else
664     ch1 = *lpszSearch;
665   iLen = lstrlenA(lpszSearch);
666 
667   if (!lpszEnd)
668     lpszEnd = lpszStr + lstrlenA(lpszStr);
669   else /* reproduce the broken behaviour on Windows */
670     lpszEnd += min(iLen - 1, lstrlenA(lpszEnd));
671 
672   while (lpszStr + iLen <= lpszEnd && *lpszStr)
673   {
674     ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | (UCHAR)lpszStr[1] : *lpszStr;
675     if (!COMCTL32_ChrCmpIA(ch1, ch2))
676     {
677       if (!StrCmpNIA(lpszStr, lpszSearch, iLen))
678         lpszRet = (LPSTR)lpszStr;
679     }
680     lpszStr = CharNextA(lpszStr);
681   }
682   return lpszRet;
683 }
684 
685 /*************************************************************************
686  * StrRStrIW	[COMCTL32.373]
687  *
688  * See StrRStrIA.
689  */
690 LPWSTR WINAPI StrRStrIW(LPCWSTR lpszStr, LPCWSTR lpszEnd, LPCWSTR lpszSearch)
691 {
692   LPWSTR lpszRet = NULL;
693   INT iLen;
694 
695   TRACE("(%s,%s)\n", debugstr_w(lpszStr), debugstr_w(lpszSearch));
696 
697   if (!lpszStr || !lpszSearch || !*lpszSearch)
698     return NULL;
699 
700   iLen = strlenW(lpszSearch);
701 
702   if (!lpszEnd)
703     lpszEnd = lpszStr + strlenW(lpszStr);
704   else /* reproduce the broken behaviour on Windows */
705     lpszEnd += min(iLen - 1, lstrlenW(lpszEnd));
706 
707 
708   while (lpszStr + iLen <= lpszEnd && *lpszStr)
709   {
710     if (!COMCTL32_ChrCmpIW(*lpszSearch, *lpszStr))
711     {
712       if (!StrCmpNIW(lpszStr, lpszSearch, iLen))
713         lpszRet = (LPWSTR)lpszStr;
714     }
715     lpszStr++;
716   }
717   return lpszRet;
718 }
719 
720 /*************************************************************************
721  * StrCSpnIA	[COMCTL32.374]
722  *
723  * Find the length of the start of a string that does not contain certain
724  * characters, ignoring case.
725  *
726  * PARAMS
727  *  lpszStr   [I] String to search
728  *  lpszMatch [I] Characters that cannot be in the substring
729  *
730  * RETURNS
731  *  The length of the part of lpszStr containing only chars not in lpszMatch,
732  *  or 0 if any parameter is invalid.
733  */
734 int WINAPI StrCSpnIA(LPCSTR lpszStr, LPCSTR lpszMatch)
735 {
736   TRACE("(%s,%s)\n",debugstr_a(lpszStr), debugstr_a(lpszMatch));
737 
738   return COMCTL32_StrSpnHelperA(lpszStr, lpszMatch, StrChrIA, TRUE);
739 }
740 
741 /*************************************************************************
742  * StrCSpnIW	[COMCTL32.375]
743  *
744  * See StrCSpnIA.
745  */
746 int WINAPI StrCSpnIW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
747 {
748   LPCWSTR lpszRead = lpszStr;
749 
750   TRACE("(%s,%s)\n",debugstr_w(lpszStr), debugstr_w(lpszMatch));
751 
752   if (lpszStr && *lpszStr && lpszMatch)
753   {
754     while (*lpszRead)
755     {
756       if (StrChrIW(lpszMatch, *lpszRead)) break;
757       lpszRead++;
758     }
759   }
760   return lpszRead - lpszStr;
761 }
762 
763 /**************************************************************************
764  * StrRChrIA	[COMCTL32.368]
765  *
766  * Find the last occurrence of a character in string, ignoring case.
767  *
768  * PARAMS
769  *  lpszStr [I] String to search in
770  *  lpszEnd [I] Place to end search, or NULL to search until the end of lpszStr
771  *  ch      [I] Character to search for.
772  *
773  * RETURNS
774  *  Success: A pointer to the last occurrence of ch in lpszStr before lpszEnd,
775  *           or NULL if not found.
776  *  Failure: NULL, if any arguments are invalid.
777  */
778 LPSTR WINAPI StrRChrIA(LPCSTR lpszStr, LPCSTR lpszEnd, WORD ch)
779 {
780   LPCSTR lpszRet = NULL;
781 
782   TRACE("(%s,%s,%x)\n", debugstr_a(lpszStr), debugstr_a(lpszEnd), ch);
783 
784   if (lpszStr)
785   {
786     WORD ch2;
787 
788     if (!lpszEnd)
789       lpszEnd = lpszStr + lstrlenA(lpszStr);
790 
791     while (*lpszStr && lpszStr <= lpszEnd)
792     {
793       ch2 = IsDBCSLeadByte(*lpszStr)? *lpszStr << 8 | lpszStr[1] : *lpszStr;
794 
795       if (ch == ch2)
796         lpszRet = lpszStr;
797       lpszStr = CharNextA(lpszStr);
798     }
799   }
800   return (LPSTR)lpszRet;
801 }
802 
803 /**************************************************************************
804  * StrRChrIW	[COMCTL32.369]
805  *
806  * See StrRChrIA.
807  */
808 LPWSTR WINAPI StrRChrIW(LPCWSTR str, LPCWSTR end, WORD ch)
809 {
810     WCHAR *ret = NULL;
811 
812     if (!str) return NULL;
813     if (!end) end = str + strlenW(str);
814     while (str < end)
815     {
816         if (!COMCTL32_ChrCmpIW(*str, ch)) ret = (WCHAR *)str;
817         str++;
818     }
819     return ret;
820 }
821 
822 /*************************************************************************
823  * StrCSpnW	[COMCTL32.364]
824  *
825  * See StrCSpnA.
826  */
827 int WINAPI StrCSpnW(LPCWSTR lpszStr, LPCWSTR lpszMatch)
828 {
829     if (!lpszStr || !lpszMatch) return 0;
830     return strcspnW( lpszStr, lpszMatch );
831 }
832 
833 /*************************************************************************
834  * IntlStrEqWorkerA	[COMCTL32.376]
835  *
836  * Compare two strings.
837  *
838  * PARAMS
839  *  bCase    [I] Whether to compare case sensitively
840  *  lpszStr  [I] First string to compare
841  *  lpszComp [I] Second string to compare
842  *  iLen     [I] Length to compare
843  *
844  * RETURNS
845  *  TRUE  If the strings are equal.
846  *  FALSE Otherwise.
847  */
848 BOOL WINAPI IntlStrEqWorkerA(BOOL bCase, LPCSTR lpszStr, LPCSTR lpszComp,
849                              int iLen)
850 {
851   DWORD dwFlags;
852   int iRet;
853 
854   TRACE("(%d,%s,%s,%d)\n", bCase,
855         debugstr_a(lpszStr), debugstr_a(lpszComp), iLen);
856 
857   /* FIXME: This flag is undocumented and unknown by our CompareString.
858    */
859   dwFlags = LOCALE_RETURN_GENITIVE_NAMES;
860   if (!bCase) dwFlags |= NORM_IGNORECASE;
861 
862   iRet = CompareStringA(GetThreadLocale(),
863                         dwFlags, lpszStr, iLen, lpszComp, iLen);
864 
865   if (!iRet)
866     iRet = CompareStringA(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);
867 
868   return iRet == CSTR_EQUAL;
869 }
870 
871 /*************************************************************************
872  * IntlStrEqWorkerW	[COMCTL32.377]
873  *
874  * See IntlStrEqWorkerA.
875  */
876 BOOL WINAPI IntlStrEqWorkerW(BOOL bCase, LPCWSTR lpszStr, LPCWSTR lpszComp,
877                              int iLen)
878 {
879   DWORD dwFlags;
880   int iRet;
881 
882   TRACE("(%d,%s,%s,%d)\n", bCase,
883         debugstr_w(lpszStr),debugstr_w(lpszComp), iLen);
884 
885   /* FIXME: This flag is undocumented and unknown by our CompareString.
886    */
887   dwFlags = LOCALE_RETURN_GENITIVE_NAMES;
888   if (!bCase) dwFlags |= NORM_IGNORECASE;
889 
890   iRet = CompareStringW(GetThreadLocale(),
891                         dwFlags, lpszStr, iLen, lpszComp, iLen);
892 
893   if (!iRet)
894     iRet = CompareStringW(2048, dwFlags, lpszStr, iLen, lpszComp, iLen);
895 
896   return iRet == CSTR_EQUAL;
897 }
898