1 /** @file strutil.c String and text utilities.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #include "de/strutil.h"
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <ctype.h>
27 
28 #if WIN32
29 # define strcasecmp _stricmp
30 #else
31 # include <strings.h>
32 #endif
33 
dd_vsnprintf(char * str,size_t size,char const * format,va_list ap)34 int dd_vsnprintf(char *str, size_t size, char const *format, va_list ap)
35 {
36     int result = vsnprintf(str, size, format, ap);
37 
38 #ifdef WIN32
39     // Always terminate.
40     str[size - 1] = 0;
41     return result;
42 #else
43     return result >= (int)size? -1 : (int)size;
44 #endif
45 }
46 
dd_snprintf(char * str,size_t size,char const * format,...)47 int dd_snprintf(char *str, size_t size, char const *format, ...)
48 {
49     int result = 0;
50 
51     va_list args;
52     va_start(args, format);
53     result = dd_vsnprintf(str, size, format, args);
54     va_end(args);
55 
56     return result;
57 }
58 
59 #ifdef WIN32
strcasestr(const char * text,const char * sub)60 const char* strcasestr(const char *text, const char *sub)
61 {
62     int textLen = strlen(text);
63     int subLen = strlen(sub);
64     int i;
65 
66     for (i = 0; i <= textLen - subLen; ++i)
67     {
68         const char* start = text + i;
69         if (!_strnicmp(start, sub, subLen)) return start;
70     }
71     return 0;
72 }
73 #endif
74 
75 #ifdef UNIX
strupr(char * string)76 char* strupr(char* string)
77 {
78     char* ch = string;
79     for (; *ch; ch++) *ch = toupper(*ch);
80     return string;
81 }
strlwr(char * string)82 char* strlwr(char* string)
83 {
84     char* ch = string;
85     for (; *ch; ch++) *ch = tolower(*ch);
86     return string;
87 }
88 #endif // UNIX
89 
M_SkipWhite(const char * str)90 char *M_SkipWhite(const char *str)
91 {
92     while (*str && DENG_ISSPACE(*str))
93         str++;
94     return (char *) str;
95 }
96 
M_FindWhite(const char * str)97 char *M_FindWhite(const char *str)
98 {
99     while (*str && !DENG_ISSPACE(*str))
100         str++;
101     return (char *) str;
102 }
103 
M_StripLeft(char * str)104 void M_StripLeft(char* str)
105 {
106     size_t len, num;
107     if (NULL == str || !str[0]) return;
108 
109     len = strlen(str);
110     // Count leading whitespace characters.
111     num = 0;
112     while (num < len && isspace(str[num]))
113         ++num;
114     if (0 == num) return;
115 
116     // Remove 'num' characters.
117     memmove(str, str + num, len - num);
118     str[len] = 0;
119 }
120 
M_StripRight(char * str,size_t len)121 void M_StripRight(char* str, size_t len)
122 {
123     char* end;
124     int numZeroed = 0;
125     if (NULL == str || 0 == len) return;
126 
127     end = str + strlen(str) - 1;
128     while (end >= str && isspace(*end))
129     {
130         end--;
131         numZeroed++;
132     }
133     memset(end + 1, 0, numZeroed);
134 }
135 
M_Strip(char * str,size_t len)136 void M_Strip(char* str, size_t len)
137 {
138     M_StripLeft(str);
139     M_StripRight(str, len);
140 }
141 
M_SkipLine(char * str)142 char* M_SkipLine(char* str)
143 {
144     while (*str && *str != '\n')
145         str++;
146     // If the newline was found, skip it, too.
147     if (*str == '\n')
148         str++;
149     return str;
150 }
151 
M_IsStringValidInt(const char * str)152 dd_bool M_IsStringValidInt(const char* str)
153 {
154     size_t i, len;
155     const char* c;
156     dd_bool isBad;
157 
158     if (!str)
159         return false;
160 
161     len = strlen(str);
162     if (len == 0)
163         return false;
164 
165     for (i = 0, c = str, isBad = false; i < len && !isBad; ++i, c++)
166     {
167         if (i != 0 && *c == '-')
168             isBad = true; // sign is in the wrong place.
169         else if (*c < '0' || *c > '9')
170             isBad = true; // non-numeric character.
171     }
172 
173     return !isBad;
174 }
175 
M_IsStringValidByte(const char * str)176 dd_bool M_IsStringValidByte(const char* str)
177 {
178     if (M_IsStringValidInt(str))
179     {
180         int val = atoi(str);
181         if (!(val < 0 || val > 255))
182             return true;
183     }
184     return false;
185 }
186 
M_IsStringValidFloat(const char * str)187 dd_bool M_IsStringValidFloat(const char* str)
188 {
189     size_t i, len;
190     const char* c;
191     dd_bool isBad, foundDP = false;
192 
193     if (!str)
194         return false;
195 
196     len = strlen(str);
197     if (len == 0)
198         return false;
199 
200     for (i = 0, c = str, isBad = false; i < len && !isBad; ++i, c++)
201     {
202         if (i != 0 && *c == '-')
203             isBad = true; // sign is in the wrong place.
204         else if (*c == '.')
205         {
206             if (foundDP)
207                 isBad = true; // multiple decimal places??
208             else
209                 foundDP = true;
210         }
211         else if (*c < '0' || *c > '9')
212             isBad = true; // other non-numeric character.
213     }
214 
215     return !isBad;
216 }
217 
M_StrCat(char * buf,const char * str,size_t bufSize)218 char* M_StrCat(char* buf, const char* str, size_t bufSize)
219 {
220     return M_StrnCat(buf, str, strlen(str), bufSize);
221 }
222 
M_StrnCat(char * buf,const char * str,size_t nChars,size_t bufSize)223 char* M_StrnCat(char* buf, const char* str, size_t nChars, size_t bufSize)
224 {
225     int n = nChars;
226     int destLen = strlen(buf);
227     if ((int)bufSize - destLen - 1 < n)
228     {
229         // Cannot copy more than fits in the buffer.
230         // The 1 is for the null character.
231         n = bufSize - destLen - 1;
232     }
233     if (n <= 0) return buf; // No space left.
234     return strncat(buf, str, n);
235 }
236 
M_StrCatQuoted(char * dest,const char * src,size_t len)237 char* M_StrCatQuoted(char* dest, const char* src, size_t len)
238 {
239     size_t k = strlen(dest) + 1, i;
240 
241     strncat(dest, "\"", len);
242     for (i = 0; src[i]; i++)
243     {
244         if (src[i] == '"')
245         {
246             strncat(dest, "\\\"", len);
247             k += 2;
248         }
249         else
250         {
251             dest[k++] = src[i];
252             dest[k] = 0;
253         }
254     }
255     strncat(dest, "\"", len);
256 
257     return dest;
258 }
259 
M_LimitedStrCat(char * buf,const char * str,size_t maxWidth,char separator,size_t bufLength)260 char* M_LimitedStrCat(char* buf, const char* str, size_t maxWidth,
261                       char separator, size_t bufLength)
262 {
263     dd_bool         isEmpty = !buf[0];
264     size_t          length;
265 
266     // How long is this name?
267     length = MIN_OF(maxWidth, strlen(str));
268 
269     // A separator is included if this is not the first name.
270     if (separator && !isEmpty)
271         ++length;
272 
273     // Does it fit?
274     if (strlen(buf) + length < bufLength)
275     {
276         if (separator && !isEmpty)
277         {
278             char            sepBuf[2];
279 
280             sepBuf[0] = separator;
281             sepBuf[1] = 0;
282 
283             strcat(buf, sepBuf);
284         }
285         strncat(buf, str, length);
286     }
287 
288     return buf;
289 }
290 
M_ForceUppercase(char * text)291 void M_ForceUppercase(char *text)
292 {
293     char c;
294 
295     while ((c = *text) != 0)
296     {
297         if (c >= 'a' && c <= 'z')
298         {
299             *text++ = c - ('a' - 'A');
300         }
301         else
302         {
303             text++;
304         }
305     }
306 }
307 
M_StrTok(char ** cursor,const char * delimiters)308 char* M_StrTok(char** cursor, const char* delimiters)
309 {
310     char* begin = *cursor;
311 
312     while (**cursor && !strchr(delimiters, **cursor))
313         (*cursor)++;
314 
315     if (**cursor)
316     {
317         // Stop here.
318         **cursor = 0;
319 
320         // Advance one more so we'll start from the right character on
321         // the next call.
322         (*cursor)++;
323     }
324 
325     return begin;
326 }
327 
M_TrimmedFloat(float val)328 char* M_TrimmedFloat(float val)
329 {
330     static char trimmedFloatBuffer[32];
331     char* ptr = trimmedFloatBuffer;
332 
333     sprintf(ptr, "%f", val);
334     // Get rid of the extra zeros.
335     for (ptr += strlen(ptr) - 1; ptr >= trimmedFloatBuffer; ptr--)
336     {
337         if (*ptr == '0')
338             *ptr = 0;
339         else if (*ptr == '.')
340         {
341             *ptr = 0;
342             break;
343         }
344         else
345             break;
346     }
347     return trimmedFloatBuffer;
348 }
349