1 | // tString.cpp  |
2 | //  |
3 | // tString is a simple and readable string class that implements sensible operators, including implicit casts. There is  |
4 | // no UCS2 or UTF16 support since UTF8 is, in my opinion, superior and the way forward. tStrings will work with UTF8.  |
5 | // You cannot stream (from cin etc) more than 512 chars into a string. This restriction is only for wacky << streaming.  |
6 | //  |
7 | // Copyright (c) 2004-2006, 2015, 2017, 2020 Tristan Grimmer.  |
8 | // Copyright (c) 2020 Stefan Wessels.  |
9 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
10 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
11 | //  |
12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
13 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
14 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
15 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
16 | // PERFORMANCE OF THIS SOFTWARE.  |
17 |   |
18 | #include "Foundation/tString.h"  |
19 | #include "Foundation/tStandard.h"  |
20 | #include "Foundation/tHash.h"  |
21 |   |
22 |   |
23 | char tString::EmptyChar = '\0';  |
24 |   |
25 |   |
26 | tString::operator uint32()  |
27 | {  |
28 | return tHash::tHashStringFast32(TextData);  |
29 |   |
30 | }  |
31 |   |
32 |   |
33 | tString::operator uint32() const  |
34 | {  |
35 | return tHash::tHashStringFast32(TextData);  |
36 | }  |
37 |   |
38 |   |
39 | tString tString::Left(const char c) const  |
40 | {  |
41 | int pos = FindChar(c);  |
42 | if (pos == -1)  |
43 | return *this;  |
44 | if (pos == 0)  |
45 | return tString();  |
46 |   |
47 | // Remember, this zeros the memory, so tStrncpy not dealing with the terminating null is ok.  |
48 | tString buf(pos);  |
49 | tStd::tStrncpy(buf.TextData, TextData, pos);  |
50 | return buf;  |
51 | }  |
52 |   |
53 |   |
54 | tString tString::Right(const char c) const  |
55 | {  |
56 | int pos = FindChar(c, true);  |
57 | if (pos == -1)  |
58 | return *this;  |
59 |   |
60 | int length = Length();  |
61 | if (pos == (length-1))  |
62 | return tString();  |
63 |   |
64 | // Remember, this zeros the memory, so tStrncpy not dealing with the terminating null is ok.  |
65 | tString buf(length - 1 - pos);  |
66 | tStd::tStrncpy(buf.TextData, TextData + pos + 1, length - 1 - pos);  |
67 | return buf;  |
68 | }  |
69 |   |
70 |   |
71 | tString tString::Left(int count) const  |
72 | {  |
73 | if(count <= 0)  |
74 | return tString();  |
75 |   |
76 | int length = Length();  |
77 | if (count > length)  |
78 | count = length;  |
79 |   |
80 | tString buf(count);  |
81 | tStd::tStrncpy(buf.TextData, TextData, count);  |
82 | return buf;  |
83 | }  |
84 |   |
85 |   |
86 | tString tString::Right(int count) const  |
87 | {  |
88 | if (count <= 0)  |
89 | return tString();  |
90 |   |
91 | int length = Length();  |
92 | int start = length - count;  |
93 | if (start < 0)  |
94 | {  |
95 | start = 0;  |
96 | count = length;  |
97 | }  |
98 |   |
99 | tString buf(count);  |
100 | tStd::tStrncpy(buf.TextData, TextData + start, count);  |
101 | return buf;  |
102 | }  |
103 |   |
104 |   |
105 | tString tString::Mid(int start, int count) const  |
106 | {  |
107 | int length = Length();  |
108 | if(start < 0 || start >= length || count <= 0)  |
109 | return tString();  |
110 |   |
111 | if(start + count > length)  |
112 | count = length - start;  |
113 |   |
114 | tString buf(count);  |
115 | tStd::tStrncpy(buf.TextData, TextData + start, count);  |
116 | return buf;  |
117 | }  |
118 |   |
119 |   |
120 | tString tString::(const char divider)  |
121 | {  |
122 | int pos = FindChar(divider);  |
123 | if (pos == -1)  |
124 | {  |
125 | tString buf(Text());  |
126 | Clear();  |
127 | return buf;  |
128 | }  |
129 |   |
130 | // Remember, this constructor zeros the memory, so strncpy not dealing with the terminating null is ok.  |
131 | tString buf(pos);  |
132 | tStd::tStrncpy(buf.TextData, TextData, pos);  |
133 |   |
134 | int length = Length();  |
135 | char* newText = new char[length-pos];  |
136 |   |
137 | // This will append the null.  |
138 | tStd::tStrncpy(newText, TextData+pos+1, length-pos);  |
139 |   |
140 | if (TextData != &EmptyChar)  |
141 | delete[] TextData;  |
142 | TextData = newText;  |
143 |   |
144 | return buf;  |
145 | }  |
146 |   |
147 |   |
148 | tString tString::(const char divider)  |
149 | {  |
150 | int pos = FindChar(divider, true);  |
151 | if (pos == -1)  |
152 | {  |
153 | tString buf(Text());  |
154 | Clear();  |
155 | return buf;  |
156 | }  |
157 |   |
158 | int wordLength = Length() - pos - 1;  |
159 |   |
160 | // Remember, this constructor zeros the memory, so strncpy not dealing with the terminating null is ok.  |
161 | tString buf(wordLength);  |
162 | tStd::tStrncpy(buf.TextData, TextData+pos+1, wordLength);  |
163 |   |
164 | char* newText = new char[pos+1];  |
165 | tStd::tStrncpy(newText, TextData, pos);  |
166 | newText[pos] = '\0';  |
167 |   |
168 | if (TextData != &EmptyChar)  |
169 | delete[] TextData;  |
170 | TextData = newText;  |
171 |   |
172 | return buf;  |
173 | }  |
174 |   |
175 |   |
176 | tString tString::(int count)  |
177 | {  |
178 | int length = Length();  |
179 | if (count > length)  |
180 | count = length;  |
181 |   |
182 | if(count <= 0)  |
183 | return tString();  |
184 |   |
185 | tString left(count);  |
186 | tStd::tStrncpy(left.TextData, TextData, count);  |
187 |   |
188 | // Source string is known not to be empty now  |
189 | int newLength = length - count;  |
190 | if (newLength == 0)  |
191 | {  |
192 | delete TextData;  |
193 | TextData = &EmptyChar;  |
194 | return left;  |
195 | }  |
196 |   |
197 | char* newText = new char[newLength+1];  |
198 | strcpy(newText, TextData+count);  |
199 |   |
200 | delete[] TextData;  |
201 | TextData = newText;  |
202 |   |
203 | return left;  |
204 | }  |
205 |   |
206 |   |
207 | tString tString::(int count)  |
208 | {  |
209 | int length = Length();  |
210 | int start = length - count;  |
211 | if(start < 0)  |
212 | {  |
213 | start = 0;  |
214 | count = length;  |
215 | }  |
216 |   |
217 | if(count <= 0)  |
218 | return tString();  |
219 |   |
220 | tString right(count);  |
221 | tStd::tStrncpy(right.TextData, TextData + start, count);  |
222 |   |
223 | // Source string is known not to be empty now  |
224 | int newLength = length - count;  |
225 | if (newLength == 0)  |
226 | {  |
227 | delete TextData;  |
228 | TextData = &EmptyChar;  |
229 | return right;  |
230 | }  |
231 |   |
232 | char* newText = new char[newLength+1];  |
233 | TextData[length - count] = '\0';  |
234 |   |
235 | tStd::tStrcpy(newText, TextData);  |
236 |   |
237 | delete[] TextData;  |
238 | TextData = newText;  |
239 |   |
240 | return right;  |
241 | }  |
242 |   |
243 |   |
244 | tString tString::(int start, int count)  |
245 | {  |
246 | int length = Length();  |
247 | if(start < 0 || start >= length || count <= 0)  |
248 | return tString();  |
249 |   |
250 | if(start + count > length)  |
251 | count = length - start;  |
252 |   |
253 | tString mid(count);  |
254 | tStd::tStrncpy(mid.TextData, TextData + start, count);  |
255 |   |
256 | int newLength = length - count;  |
257 | if(newLength == 0)  |
258 | {  |
259 | delete TextData;  |
260 | TextData = &EmptyChar;  |
261 | return mid;  |
262 | }  |
263 |   |
264 | char* newText = new char[newLength+1];  |
265 | newText[newLength] = '\0';  |
266 |   |
267 | tStd::tStrncpy(newText, TextData, start);  |
268 | tStd::tStrncpy(newText+start, TextData+start+count, newLength-start);  |
269 |   |
270 | delete[] TextData;  |
271 | TextData = newText;  |
272 |   |
273 | return mid;  |
274 | }  |
275 |   |
276 |   |
277 | int tString::Replace(const char* s, const char* r)  |
278 | {  |
279 | if (!s || (s[0] == '\0'))  |
280 | return 0;  |
281 |   |
282 | int origTextLength = tStd::tStrlen(TextData);  |
283 | int searchStringLength = tStd::tStrlen(s);  |
284 | int replaceStringLength = r ? tStd::tStrlen(r) : 0;  |
285 | int replaceCount = 0;  |
286 |   |
287 | if (searchStringLength != replaceStringLength)  |
288 | {  |
289 | // Since the replacement string is a different size, we'll need to reallocate  |
290 | // out memory. We start by finding out how many replacements we will need to do.  |
291 | char* searchStart = TextData;  |
292 |   |
293 | while (searchStart < (TextData + origTextLength))  |
294 | {  |
295 | char* foundString = tStd::tStrstr(searchStart, s);  |
296 | if (!foundString)  |
297 | break;  |
298 |   |
299 | replaceCount++;  |
300 | searchStart = foundString + searchStringLength;  |
301 | }  |
302 |   |
303 | // The new length may be bigger or smaller than the original. If the newlength is precisely  |
304 | // 0, it means that the entire string is being replaced with nothing, so we can exit early.  |
305 | // Ex. Replace "abcd" in "abcdabcd" with ""  |
306 | int newTextLength = origTextLength + replaceCount*(replaceStringLength - searchStringLength);  |
307 | if (!newTextLength)  |
308 | {  |
309 | if (TextData != &EmptyChar)  |
310 | delete[] TextData;  |
311 | TextData = &EmptyChar;  |
312 | return replaceCount;  |
313 | }  |
314 |   |
315 | char* newText = new char[newTextLength + 16];  |
316 | newText[newTextLength] = '\0';  |
317 |   |
318 | tStd::tMemset( newText, 0, newTextLength + 16 );  |
319 |   |
320 | int newTextWritePos = 0;  |
321 |   |
322 | searchStart = TextData;  |
323 | while (searchStart < (TextData + origTextLength))  |
324 | {  |
325 | char* foundString = tStd::tStrstr(searchStart, s);  |
326 |   |
327 | if (foundString)  |
328 | {  |
329 | tStd::tMemcpy(newText+newTextWritePos, searchStart, int(foundString-searchStart));  |
330 | newTextWritePos += int(foundString-searchStart);  |
331 |   |
332 | tStd::tMemcpy(newText+newTextWritePos, r, replaceStringLength);  |
333 | newTextWritePos += replaceStringLength;  |
334 | }  |
335 | else  |
336 | {  |
337 | tStd::tStrcpy(newText+newTextWritePos, searchStart);  |
338 | break;  |
339 | }  |
340 |   |
341 | searchStart = foundString + searchStringLength;  |
342 | }  |
343 |   |
344 | if (TextData != &EmptyChar)  |
345 | delete[] TextData;  |
346 | TextData = newText;  |
347 | }  |
348 | else  |
349 | {  |
350 | // In this case the replacement string is exactly the same length at the search string.  |
351 | // Much easier to deal with and no need for memory allocation.  |
352 | char* searchStart = TextData;  |
353 |   |
354 | while (searchStart < (TextData + origTextLength))  |
355 | {  |
356 | char* foundString = tStd::tStrstr(searchStart, s);  |
357 | if (foundString)  |
358 | {  |
359 | tStd::tMemcpy(foundString, r, replaceStringLength);  |
360 | replaceCount++;  |
361 | }  |
362 | else  |
363 | {  |
364 | break;  |
365 | }  |
366 |   |
367 | searchStart = foundString + searchStringLength;  |
368 | }  |
369 | }  |
370 |   |
371 | return replaceCount;  |
372 | }  |
373 |   |
374 |   |
375 | int tString::Remove(const char c)  |
376 | {  |
377 | int destIndex = 0;  |
378 | int numRemoved = 0;  |
379 |   |
380 | // This operation can be done in place.  |
381 | for (int i = 0; i < Length(); i++)  |
382 | {  |
383 | if (TextData[i] != c)  |
384 | {  |
385 | TextData[destIndex] = TextData[i];  |
386 | destIndex++;  |
387 | }  |
388 | else  |
389 | {  |
390 | numRemoved++;  |
391 | }  |
392 | }  |
393 | TextData[destIndex] = '\0';  |
394 |   |
395 | return numRemoved;  |
396 | }  |
397 |   |
398 |   |
399 | int tString::RemoveLeading(const char* removeThese)  |
400 | {  |
401 | if (!TextData || (TextData == &EmptyChar) || !removeThese)  |
402 | return 0;  |
403 |   |
404 | int cnt = 0;  |
405 | while (TextData[cnt])  |
406 | {  |
407 | bool matches = false;  |
408 | int j = 0;  |
409 | while (removeThese[j] && !matches)  |
410 | {  |
411 | if (removeThese[j] == TextData[cnt])  |
412 | matches = true;  |
413 | j++;  |
414 | }  |
415 | if (matches)   |
416 | cnt++;  |
417 | else  |
418 | break;  |
419 | }  |
420 |   |
421 | if (cnt > 0)  |
422 | {  |
423 | int oldlen = Length();  |
424 | char* newtext = new char[oldlen-cnt+1];  |
425 | tStd::tStrcpy(newtext, &TextData[cnt]);  |
426 | TextData = newtext;  |
427 | }  |
428 |   |
429 | return cnt;  |
430 | }  |
431 |   |
432 |   |
433 | int tString::RemoveTrailing(const char* removeThese)  |
434 | {  |
435 | if (!TextData || (TextData == &EmptyChar) || !removeThese)  |
436 | return 0;  |
437 |   |
438 | int oldlen = Length();  |
439 |   |
440 | int i = oldlen - 1;  |
441 | while (i > -1)  |
442 | {  |
443 | bool matches = false;  |
444 | int j = 0;  |
445 | while (removeThese[j] && !matches)  |
446 | {  |
447 | if (removeThese[j] == TextData[i])  |
448 | matches = true;  |
449 | j++;  |
450 | }  |
451 | if (matches)   |
452 | i--;  |
453 | else  |
454 | break;  |
455 | }  |
456 | int numRemoved = oldlen - i;  |
457 | TextData[i+1] = '\0';  |
458 |   |
459 | return numRemoved;  |
460 | }  |
461 |   |
462 |   |
463 | int tStd::tExplode(tList<tStringItem>& components, const tString& src, char divider)  |
464 | {  |
465 | tString source = src;  |
466 | int startCount = components.GetNumItems();  |
467 | while (source.FindChar(divider) != -1)  |
468 | {  |
469 | tString component = source.ExtractLeft(divider);  |
470 | components.Append(new tStringItem(component));  |
471 | }  |
472 |   |
473 | // If there's anything left in source we need to add it.  |
474 | if (!source.IsEmpty())  |
475 | components.Append(new tStringItem(source));  |
476 |   |
477 | return components.GetNumItems() - startCount;  |
478 | }  |
479 |   |
480 |   |
481 | int tStd::tExplode(tList<tStringItem>& components, const tString& src, const tString& divider)  |
482 | {  |
483 | // Well, this is a bit of a cheezy way of doing this. We just assume that ASCII character 31,  |
484 | // the "unit separator", is meant for this kind of thing and not otherwise present in the src string.  |
485 | tString source = src;  |
486 | char sep[2];  |
487 | sep[0] = 31;  |
488 | sep[1] = 0;  |
489 | source.Replace(divider, sep);  |
490 | return tExplode(components, source, 31);  |
491 | }  |
492 | |