0
|
1 /*
|
|
2 * Miscellaneous string-handling related utility-functions
|
|
3 * Programmed and designed by Matti 'ccr' Hamalainen
|
|
4 * (C) Copyright 2002-2008 Tecnic Software productions (TNSP)
|
|
5 *
|
|
6 * Please read file 'COPYING' for information on license and distribution.
|
|
7 */
|
|
8 #ifdef HAVE_CONFIG_H
|
2
|
9 #include "config.h"
|
0
|
10 #endif
|
|
11 #include "th_string.h"
|
|
12
|
|
13 #define LPREV (pNode->pPrev)
|
|
14 #define LNEXT (pNode->pNext)
|
|
15
|
79
|
16
|
|
17 /* strdup with a NULL check
|
|
18 */
|
|
19 char *th_strdup(const char *s)
|
|
20 {
|
81
|
21 char *res;
|
|
22 if (s == NULL)
|
|
23 return NULL;
|
|
24
|
|
25 if ((res = th_malloc(strlen(s) + 1)) == NULL)
|
|
26 return NULL;
|
|
27
|
|
28 strcpy(res, s);
|
|
29 return res;
|
79
|
30 }
|
|
31
|
|
32
|
124
|
33 char *th_strncpy(char * dst, const char * src, const size_t n)
|
0
|
34 {
|
81
|
35 const char *s = src;
|
|
36 char *d = dst;
|
|
37 size_t i;
|
|
38 assert(src != NULL);
|
|
39 assert(dst != NULL);
|
0
|
40
|
81
|
41 /* Copy to the destination */
|
|
42 i = n;
|
124
|
43 while (*s && i > 0) {
|
81
|
44 *(d++) = *(s++);
|
|
45 i--;
|
|
46 }
|
0
|
47
|
81
|
48 /* Fill rest of space with zeros */
|
|
49 while (i > 0) {
|
|
50 *(d++) = 0;
|
|
51 i--;
|
|
52 }
|
0
|
53
|
81
|
54 /* Ensure that last is always zero */
|
|
55 dst[n - 1] = 0;
|
0
|
56
|
81
|
57 return dst;
|
0
|
58 }
|
|
59
|
|
60
|
124
|
61 #ifdef STRDUP_PRINTF
|
|
62 /*
|
|
63 */
|
|
64 enum {
|
|
65 TH_PAD_RIGHT = 1,
|
|
66 TH_PAD_ZERO = 2
|
|
67 };
|
|
68
|
|
69
|
|
70 static size_t th_printbuf_str(char **buf, const char *str, size_t width, int flags)
|
|
71 {
|
|
72 size_t len = strlen(str);
|
|
73 char *out = (buf != NULL) ? *buf : NULL;
|
|
74 char pad = ' ';
|
|
75
|
|
76 if (width > 0) {
|
|
77 width = (len >= width) ? 0 : width - len;
|
|
78 if (flags & TH_PAD_ZERO)
|
|
79 pad = '0';
|
|
80 }
|
|
81
|
|
82 if ((flags & TH_PAD_RIGHT) == 0 && out != NULL) {
|
|
83 while (width-- > 0)
|
|
84 *out++ = pad;
|
|
85 }
|
|
86
|
|
87 if (out != NULL) {
|
|
88 while (*str)
|
|
89 *out++ = *str++;
|
|
90 }
|
|
91
|
|
92 if (flags & TH_PAD_RIGHT && out != NULL) {
|
|
93 while (width-- > 0)
|
|
94 *out++ = pad;
|
|
95 }
|
|
96
|
|
97 if (buf != NULL)
|
|
98 *buf = out;
|
|
99
|
|
100 return len + width;
|
|
101 }
|
|
102
|
|
103 #define TH_INTBUF_LEN (32)
|
|
104
|
|
105 static size_t th_printbuf_int(char **buf, int i, int b, int sg, int width, int pad, int letbase)
|
0
|
106 {
|
124
|
107 char tmpbuf[TH_INTBUF_LEN], *s;
|
|
108 int t, neg = 0, pc = 0;
|
|
109 unsigned int u = i;
|
|
110
|
|
111 if (i == 0) {
|
|
112 tmpbuf[0] = '0';
|
|
113 tmpbuf[1] = 0;
|
|
114 return th_printbuf_str(buf, tmpbuf, width, pad);
|
|
115 }
|
|
116
|
|
117 if (sg && b == 10 && i < 0) {
|
|
118 neg = 1;
|
|
119 u = -i;
|
|
120 }
|
|
121
|
|
122 s = tmpbuf + TH_INTBUF_LEN - 1;
|
|
123 *s = 0;
|
|
124
|
|
125 while (u) {
|
|
126 t = u % b;
|
|
127 if (t >= 10)
|
|
128 t += letbase - '0' - 10;
|
|
129
|
|
130 *--s = t + '0';
|
|
131 u /= b;
|
|
132 }
|
|
133
|
|
134 if (neg) {
|
|
135 if (width && (pad & PAD_ZERO)) {
|
|
136 printchar (out, '-');
|
|
137 ++pc;
|
|
138 --width;
|
|
139 } else {
|
|
140 *--s = '-';
|
|
141 }
|
|
142 }
|
|
143
|
|
144 return pc + th_printbuf_str(buf, s, width, pad);
|
|
145 }
|
|
146
|
|
147
|
|
148 char * th_strdup_vprintf(const char *fmt, va_list args)
|
|
149 {
|
|
150 const char *s = fmt;
|
|
151 char *res;
|
|
152 size_t len = 0;
|
|
153
|
|
154 /* 1. Determine required space for final string */
|
|
155 while (*s) {
|
|
156 if (*s == '%') {
|
|
157 s++;
|
|
158 } else {
|
|
159 s++;
|
|
160 len++;
|
|
161 }
|
|
162 }
|
|
163
|
|
164 /* 2. Allocate space */
|
|
165
|
|
166 /* 3. Create final string */
|
|
167
|
|
168 return res;
|
|
169 }
|
|
170
|
|
171
|
|
172 char * th_strdup_printf(const char *fmt, ...)
|
|
173 {
|
|
174 char *res;
|
|
175 va_list ap;
|
|
176
|
|
177 va_start(ap, fmt);
|
|
178 res = th_strdup_vprintf(fmt, ap);
|
|
179 va_end(ap);
|
|
180
|
|
181 return res;
|
|
182 }
|
|
183 #endif
|
|
184
|
|
185 /* Compare two strings ignoring case [strcasecmp, strncasecmp]
|
|
186 */
|
|
187 int th_strcasecmp(const char * str1, const char * str2)
|
|
188 {
|
|
189 const char *s1 = str1, *s2 = str2;
|
81
|
190 assert(str1 != NULL);
|
|
191 assert(str2 != NULL);
|
0
|
192
|
81
|
193 if (str1 == str2)
|
|
194 return 0;
|
0
|
195
|
124
|
196 while (*s1 && *s2 && th_tolower(*s1) == th_tolower(*s2)) {
|
81
|
197 s1++;
|
|
198 s2++;
|
|
199 }
|
0
|
200
|
81
|
201 return (th_tolower(*s1) - th_tolower(*s2));
|
0
|
202 }
|
|
203
|
|
204
|
124
|
205 int th_strncasecmp(const char * str1, const char * str2, size_t n)
|
0
|
206 {
|
124
|
207 const char *s1 = str1, *s2 = str2;
|
81
|
208 assert(str1 != NULL);
|
|
209 assert(str2 != NULL);
|
0
|
210
|
81
|
211 if (str1 == str2)
|
|
212 return 0;
|
0
|
213
|
124
|
214 while (n > 0 && *s1 && *s2 && th_tolower(*s1) == th_tolower(*s2)) {
|
81
|
215 s1++;
|
|
216 s2++;
|
|
217 n--;
|
|
218 }
|
0
|
219
|
124
|
220 return n > 0 ? (th_tolower(*s1) - th_tolower(*s2)) : 0;
|
0
|
221 }
|
|
222
|
|
223
|
|
224 /* Remove all occurences of control characters, in-place.
|
|
225 * Resulting string is always shorter or same length than original.
|
|
226 */
|
11
|
227 void th_strip_ctrlchars(char * str)
|
0
|
228 {
|
81
|
229 char *i, *j;
|
|
230 assert(str != NULL);
|
0
|
231
|
81
|
232 i = str;
|
|
233 j = str;
|
|
234 while (*i) {
|
|
235 if (!th_iscntrl(*i))
|
|
236 *(j++) = *i;
|
|
237 i++;
|
|
238 }
|
0
|
239
|
81
|
240 *j = 0;
|
0
|
241 }
|
|
242
|
|
243
|
11
|
244 /* Copy a given string over in *result.
|
0
|
245 */
|
124
|
246 int th_pstrcpy(char ** result, const char * str)
|
0
|
247 {
|
81
|
248 assert(result != NULL);
|
0
|
249
|
81
|
250 if (str == NULL)
|
|
251 return -1;
|
0
|
252
|
81
|
253 th_free(*result);
|
124
|
254 if ((*result = th_malloc(strlen(str) + 1)) == NULL)
|
81
|
255 return -2;
|
0
|
256
|
81
|
257 strcpy(*result, str);
|
|
258 return 0;
|
0
|
259 }
|
|
260
|
|
261
|
11
|
262 /* Concatenates a given string into string pointed by *result.
|
0
|
263 */
|
124
|
264 int th_pstrcat(char ** result, const char * str)
|
0
|
265 {
|
81
|
266 assert(result != NULL);
|
0
|
267
|
81
|
268 if (str == NULL)
|
|
269 return -1;
|
0
|
270
|
81
|
271 if (*result != NULL) {
|
124
|
272 *result = th_realloc(*result, strlen(*result) + strlen(str) + 1);
|
81
|
273 if (*result == NULL)
|
|
274 return -1;
|
0
|
275
|
81
|
276 strcat(*result, str);
|
|
277 } else {
|
124
|
278 *result = th_malloc(strlen(str) + 1);
|
81
|
279 if (*result == NULL)
|
|
280 return -1;
|
0
|
281
|
81
|
282 strcpy(*result, str);
|
|
283 }
|
0
|
284
|
81
|
285 return 0;
|
0
|
286 }
|
|
287
|
|
288
|
|
289 /* Find next non-whitespace character in string.
|
|
290 * Updates iPos into the position of such character and
|
|
291 * returns pointer to the string.
|
|
292 */
|
124
|
293 const char *th_findnext(const char * str, size_t * pos)
|
0
|
294 {
|
81
|
295 assert(str != NULL);
|
0
|
296
|
81
|
297 /* Terminating NULL-character is not whitespace! */
|
124
|
298 while (th_isspace(str[*pos]))
|
|
299 (*pos)++;
|
|
300 return &str[*pos];
|
0
|
301 }
|
|
302
|
|
303
|
124
|
304 /* Find next sep-character from string
|
0
|
305 */
|
124
|
306 const char *th_findsep(const char * str, size_t * pos, char sep)
|
0
|
307 {
|
81
|
308 assert(str != NULL);
|
0
|
309
|
124
|
310 while (str[*pos] && str[*pos] != sep)
|
|
311 (*pos)++;
|
|
312
|
|
313 return &str[*pos];
|
0
|
314 }
|
|
315
|
|
316
|
124
|
317 /* Find next sep- or whitespace from string
|
0
|
318 */
|
124
|
319 const char *th_findseporspace(const char * str, size_t * pos, char sep)
|
0
|
320 {
|
81
|
321 assert(str != NULL);
|
0
|
322
|
124
|
323 while (!th_isspace(str[*pos]) && str[*pos] != sep)
|
|
324 (*pos)++;
|
|
325
|
|
326 return &str[*pos];
|
0
|
327 }
|
|
328
|
|
329
|
|
330 /* Compare a string to a pattern. Case-SENSITIVE version.
|
|
331 * The matching pattern can consist of any normal characters plus
|
|
332 * wildcards ? and *. "?" matches any character and "*" matches
|
|
333 * any number of characters.
|
|
334 */
|
124
|
335 BOOL th_strmatch(const char * str, const char * pattern)
|
0
|
336 {
|
81
|
337 BOOL didMatch = TRUE, isAnyMode = FALSE, isEnd = FALSE;
|
124
|
338 const char *tmpPattern = NULL;
|
0
|
339
|
81
|
340 /* Check given pattern and string */
|
|
341 if (str == NULL || pattern == NULL)
|
|
342 return FALSE;
|
0
|
343
|
81
|
344 /* Start comparision */
|
|
345 do {
|
|
346 didMatch = FALSE;
|
|
347 switch (*pattern) {
|
|
348 case '?':
|
|
349 /* Any single character matches */
|
|
350 if (*str) {
|
|
351 didMatch = TRUE;
|
|
352 pattern++;
|
|
353 str++;
|
|
354 }
|
|
355 break;
|
0
|
356
|
81
|
357 case '*':
|
|
358 didMatch = TRUE;
|
|
359 pattern++;
|
|
360 if (!*pattern)
|
|
361 isEnd = TRUE;
|
|
362 isAnyMode = TRUE;
|
|
363 tmpPattern = pattern;
|
|
364 break;
|
0
|
365
|
81
|
366 case 0:
|
|
367 if (isAnyMode) {
|
|
368 if (*str)
|
|
369 str++;
|
|
370 else
|
|
371 isEnd = TRUE;
|
|
372 } else {
|
|
373 if (*str) {
|
|
374 if (tmpPattern) {
|
|
375 isAnyMode = TRUE;
|
|
376 pattern = tmpPattern;
|
|
377 } else
|
|
378 didMatch = FALSE;
|
|
379 } else
|
|
380 isEnd = TRUE;
|
|
381 }
|
|
382 break;
|
|
383 default:
|
|
384 if (isAnyMode) {
|
|
385 if (*pattern == *str) {
|
|
386 isAnyMode = FALSE;
|
|
387 didMatch = TRUE;
|
|
388 } else {
|
|
389 if (*str) {
|
|
390 didMatch = TRUE;
|
|
391 str++;
|
|
392 }
|
|
393 }
|
|
394 } else {
|
|
395 if (*pattern == *str) {
|
|
396 didMatch = TRUE;
|
|
397 if (*pattern)
|
|
398 pattern++;
|
|
399 if (*str)
|
|
400 str++;
|
|
401 } else {
|
|
402 if (tmpPattern) {
|
|
403 didMatch = TRUE;
|
|
404 isAnyMode = TRUE;
|
|
405 pattern = tmpPattern;
|
|
406 }
|
|
407 }
|
|
408 }
|
0
|
409
|
81
|
410 if (!*str && !*pattern)
|
|
411 isEnd = TRUE;
|
|
412 break;
|
0
|
413
|
81
|
414 } /* switch */
|
0
|
415
|
81
|
416 } while (didMatch && !isEnd);
|
0
|
417
|
81
|
418 return didMatch;
|
0
|
419 }
|
|
420
|
|
421
|
|
422 /* Compare a string to a pattern. Case-INSENSITIVE version.
|
|
423 */
|
124
|
424 BOOL th_strcasematch(const char * str, const char * pattern)
|
0
|
425 {
|
81
|
426 BOOL didMatch = TRUE, isAnyMode = FALSE, isEnd = FALSE;
|
124
|
427 const char *tmpPattern = NULL;
|
0
|
428
|
81
|
429 /* Check given pattern and string */
|
|
430 if (str == NULL || pattern == NULL)
|
|
431 return FALSE;
|
0
|
432
|
81
|
433 /* Start comparision */
|
|
434 do {
|
|
435 switch (*pattern) {
|
|
436 case '?':
|
|
437 /* Any single character matches */
|
|
438 if (*str) {
|
|
439 pattern++;
|
|
440 str++;
|
|
441 } else
|
|
442 didMatch = FALSE;
|
|
443 break;
|
0
|
444
|
81
|
445 case '*':
|
|
446 pattern++;
|
|
447 if (!*pattern || *pattern == '?')
|
|
448 isEnd = TRUE;
|
|
449 isAnyMode = TRUE;
|
|
450 tmpPattern = pattern;
|
|
451 break;
|
0
|
452
|
81
|
453 case 0:
|
|
454 if (isAnyMode) {
|
|
455 if (*str)
|
|
456 str++;
|
|
457 else
|
|
458 isEnd = TRUE;
|
|
459 } else {
|
|
460 if (*str) {
|
|
461 if (tmpPattern) {
|
|
462 isAnyMode = TRUE;
|
|
463 pattern = tmpPattern;
|
|
464 } else
|
|
465 didMatch = FALSE;
|
|
466 } else
|
|
467 isEnd = TRUE;
|
|
468 }
|
|
469 break;
|
0
|
470
|
81
|
471 default:
|
|
472 if (isAnyMode) {
|
|
473 if (th_tolower(*pattern) == th_tolower(*str)) {
|
|
474 isAnyMode = FALSE;
|
|
475 } else {
|
|
476 if (*str)
|
|
477 str++;
|
|
478 else
|
|
479 didMatch = FALSE;
|
|
480 }
|
|
481 } else {
|
|
482 if (th_tolower(*pattern) == th_tolower(*str)) {
|
|
483 if (*pattern)
|
|
484 pattern++;
|
|
485 if (*str)
|
|
486 str++;
|
|
487 } else {
|
|
488 if (tmpPattern) {
|
|
489 isAnyMode = TRUE;
|
|
490 pattern = tmpPattern;
|
|
491 } else
|
|
492 didMatch = FALSE;
|
|
493 }
|
|
494 }
|
0
|
495
|
81
|
496 if (!*str && !*pattern)
|
|
497 isEnd = TRUE;
|
|
498 break;
|
0
|
499
|
81
|
500 } /* switch */
|
0
|
501
|
81
|
502 } while (didMatch && !isEnd);
|
0
|
503
|
81
|
504 return didMatch;
|
0
|
505 }
|
|
506
|