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 
23char tString::EmptyChar = '\0'
24 
25 
26tString::operator uint32() 
27
28 return tHash::tHashStringFast32(TextData); 
29 
30
31 
32 
33tString::operator uint32() const 
34
35 return tHash::tHashStringFast32(TextData); 
36
37 
38 
39tString 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 
54tString 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 
71tString 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 
86tString 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 
105tString 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 
120tString tString::ExtractLeft(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 
148tString tString::ExtractRight(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 
176tString tString::ExtractLeft(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 
207tString tString::ExtractRight(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 
244tString tString::ExtractMid(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 
277int 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 
375int 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 
399int 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 
433int 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 
463int 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 
481int 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