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