| 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 | |