source: cpp/common/util-string.cpp @ 1025

Last change on this file since 1025 was 1025, checked in by Maciej Komosinski, 4 years ago

Added a repr() function that converts a string using only printable characters or ascii codes when necessary

  • Property svn:eol-style set to native
File size: 5.6 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include "util-string.h"
6#include <stdarg.h>
7#include "nonstd_stdio.h"
8#include "nonstd.h"
9#include <assert.h>
10#include <cstdlib> //malloc()
11#ifdef USE_VIRTFILE
12#include <common/virtfile/virtfile.h>
13#endif
14#ifdef __ANDROID__
15#include <android/log.h> //only needed to print error messages related to a workaround for Android bug
16#endif
17
18string repr(const char *str)
19{
20        string out = "";
21        char hex[4];
22        while (*str)
23        {
24                if (*str >= 32 && *str < 128)
25                        out += *str;
26                else
27                {
28                        if (*str == 10) out += "\\n"; else
29                                if (*str == 13) out += "\\r"; else
30                                {
31                                        sprintf(hex, "%X", *str);
32                                        out += "\\x";
33                                        out += hex;
34                                }
35                }
36                str++;
37        }
38        return out;
39}
40
41
42string ssprintf_va(const char* format, va_list ap)
43{
44        string s; //clang crashed when this declaration was in s=buf
45        int size = 256;
46        char* buf;
47        va_list ap_copy; // "va_list ap" can only by used once by printf-type functions as they advance the current argument pointer (crashed on linux x86_64)
48        // (does not apply to SString::sprintf, it does not have the va_list variant)
49
50        //almost like SString::sprintf, but there is no common code to share because SString can use its directWrite to avoid double allocating/copying
51#ifdef USE_VSCPRINTF
52        va_copy(ap_copy, ap);
53        size = _vscprintf(format, ap_copy) + 1; //+1 for terminating null character
54        va_end(ap_copy);
55#endif
56
57        while (true)
58        {
59                buf = (char*)malloc(size);
60                assert(buf != NULL);
61                va_copy(ap_copy, ap);
62                int n = vsnprintf(buf, size, format, ap_copy);
63                va_end(ap_copy);
64
65#ifdef __ANDROID__
66                //Workaround for Android bug. /system/lib64/libc.so? maybe only arm 64-bit? "If an encoding error occurs, a negative number is returned". On some devices keeps returning -1 forever.
67                //https://github.com/android-ndk/ndk/issues/879 but unfortunately during google play tests (Firebase Test Lab) this problem turned out to be not limited to Chinese devices and occurred in Mate 9, Galaxy S9, Pixel, Pixel 2, Moto Z (even with the en_GB locale; the locale is not important but the problem seem to be utf8 non-ascii chars in the format string).
68                if (n < 0 && size >= (1 << 24)) //wants more than 16M
69                {
70                        buf[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
71                        __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "Giving up due to Android bug: vsnprintf() wants more than %d bytes, it used %zu bytes, for format='%s'", size, strlen(buf), format);
72                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
73                        va_copy(ap_copy, ap);
74                        n = vsprintf(buf, format, ap_copy); //hoping 16M is enough
75                        va_end(ap_copy);
76                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", buf);
77                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
78                        {
79                                strcpy(buf, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
80                                strcat(buf, format); //append and return the original formatting string
81                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", buf);
82                        }
83                        n = strlen(buf); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
84                }
85#endif
86
87                if (n > -1 && n < size)
88                {
89                        s = buf;
90                        free(buf);
91                        return s;
92                }
93#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
94                if (n > -1)    /* glibc 2.1 */
95                        size = n + 1; /* precisely what is needed */
96                else           /* glibc 2.0 */
97#endif
98                        size *= 2;  /* twice the old size */
99                free(buf);
100        }
101}
102
103bool str_starts_with(const char *str, const char *prefix)
104{
105        return strncmp(str, prefix, strlen(prefix)) == 0;
106}
107
108char* strmove(char *a, char *b) //strcpy that works well for overlapping strings ("Source and destination overlap")
109{
110        if (a == NULL || b == NULL)
111                return NULL;
112        memmove(a, b, strlen(b) + 1);
113        return a;
114}
115
116string ssprintf(const char* format, ...)
117{
118        va_list ap;
119        va_start(ap, format);
120        string ret = ssprintf_va(format, ap); //is it too wasteful? copying the string again... unless the compiler can handle it better
121        va_end(ap);
122        return ret;
123}
124
125string stripExt(const string& filename)
126{
127        size_t dot = filename.rfind('.');
128        if (dot == string::npos) return filename;
129        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
130        if ((sep == string::npos) || (sep < dot))
131                return filename.substr(0, dot);
132        return filename;
133}
134
135string stripFileDir(const string& filename)
136{
137        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
138        if (sep == string::npos) return filename;
139        return filename.substr(sep + 1);
140}
141
142string getFileExt(const string& filename)
143{
144        size_t dot = filename.rfind('.');
145        if (dot == string::npos) return string("");
146        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
147        if ((sep == string::npos) || (sep < dot))
148                return filename.substr(dot);
149        return string("");
150}
151
152string getFileDir(const string& filename)
153{
154        size_t slash = filename.rfind(PATH_SEPARATOR_CHAR);
155        if (slash == string::npos) return string("");
156        return filename.substr(0, slash);
157}
158
159//trimming functions, https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
160
161void ltrim_inplace(string &s)
162{
163        s.erase(s.begin(), find_if(s.begin(), s.end(), [](int ch) {
164                return !isspace(ch);
165                }));
166}
167
168void rtrim_inplace(string &s)
169{
170        s.erase(find_if(s.rbegin(), s.rend(), [](int ch) {
171                return !isspace(ch);
172                }).base(), s.end());
173}
174
175void trim_inplace(string &s)
176{
177        ltrim_inplace(s);
178        rtrim_inplace(s);
179}
180
181string ltrim(string s)
182{
183        ltrim_inplace(s);
184        return s;
185}
186
187string rtrim(string s)
188{
189        rtrim_inplace(s);
190        return s;
191}
192
193string trim(string s)
194{
195        trim_inplace(s);
196        return s;
197}
Note: See TracBrowser for help on using the repository browser.