|
12 | 12 | * -----------------------------------------------------------------------------
|
13 | 13 | */
|
14 | 14 | #include "fossil/io/cstring.h"
|
| 15 | +#include "fossil/io/output.h" |
15 | 16 | #include <ctype.h>
|
16 | 17 | #include <stdlib.h>
|
17 | 18 |
|
@@ -335,27 +336,230 @@ cstring fossil_io_cstring_pad_right(ccstring str, size_t total_length, char pad_
|
335 | 336 | }
|
336 | 337 |
|
337 | 338 | int fossil_io_cstring_icmp(ccstring str1, ccstring str2) {
|
| 339 | + // Handle NULL pointers: both NULL means equal, one NULL means less/greater |
| 340 | + if (str1 == str2) return 0; |
| 341 | + if (!str1) return (str2 && *str2) ? -1 : 0; |
| 342 | + if (!str2) return (*str1) ? 1 : 0; |
| 343 | + |
| 344 | + // Then check |
338 | 345 | while (*str1 && *str2) {
|
339 |
| - if (tolower((unsigned char)*str1) != tolower((unsigned char)*str2)) { |
340 |
| - return 0; // Not equal |
| 346 | + int c1 = tolower((unsigned char)*str1); |
| 347 | + int c2 = tolower((unsigned char)*str2); |
| 348 | + if (c1 != c2) { |
| 349 | + return c1 - c2; |
341 | 350 | }
|
342 | 351 | str1++;
|
343 | 352 | str2++;
|
344 | 353 | }
|
345 |
| - return (*str1 == '\0' && *str2 == '\0'); // Both strings must end at the same time |
| 354 | + return tolower((unsigned char)*str1) - tolower((unsigned char)*str2); |
346 | 355 | }
|
347 | 356 |
|
348 | 357 | int fossil_io_cstring_icontains(ccstring str, ccstring substr) {
|
349 |
| - const char *p = str; |
350 |
| - while (*p) { |
351 |
| - if (fossil_io_cstring_icmp(p, substr) == 1) { |
352 |
| - return 1; // Found |
| 358 | + if (!str || !substr || !*substr) return 0; |
| 359 | + size_t substr_len = strlen(substr); |
| 360 | + for (const char *p = str; *p; ++p) { |
| 361 | + size_t i = 0; |
| 362 | + while (i < substr_len && |
| 363 | + tolower((unsigned char)p[i]) == tolower((unsigned char)substr[i])) { |
| 364 | + i++; |
| 365 | + } |
| 366 | + if (i == substr_len) { |
| 367 | + return 1; // Found (case-insensitive) |
353 | 368 | }
|
354 |
| - p++; |
| 369 | + if (!p[i]) break; |
355 | 370 | }
|
356 | 371 | return 0; // Not found
|
357 | 372 | }
|
358 | 373 |
|
| 374 | +cstring fossil_io_cstring_format(ccstring format, ...) { |
| 375 | + va_list args; |
| 376 | + va_start(args, format); |
| 377 | + va_list args_copy; |
| 378 | + va_copy(args_copy, args); |
| 379 | + |
| 380 | + int length = vsnprintf(NULL, 0, format, args); |
| 381 | + va_end(args); |
| 382 | + |
| 383 | + if (length < 0) { |
| 384 | + va_end(args_copy); |
| 385 | + return NULL; |
| 386 | + } |
| 387 | + |
| 388 | + char *buffer = malloc((size_t)length + 1); |
| 389 | + if (!buffer) { |
| 390 | + va_end(args_copy); |
| 391 | + return NULL; |
| 392 | + } |
| 393 | + |
| 394 | + vsnprintf(buffer, (size_t)length + 1, format, args_copy); |
| 395 | + va_end(args_copy); |
| 396 | + return buffer; |
| 397 | +} |
| 398 | + |
| 399 | +cstring fossil_io_cstring_join(ccstring *strings, size_t count, char delimiter) { |
| 400 | + if (!strings || count == 0) return fossil_io_cstring_dup(""); |
| 401 | + |
| 402 | + size_t total = 0; |
| 403 | + for (size_t i = 0; i < count; ++i) { |
| 404 | + total += strlen(strings[i]); |
| 405 | + } |
| 406 | + total += count - 1; // space for delimiters |
| 407 | + |
| 408 | + char *result = malloc(total + 1); |
| 409 | + if (!result) return NULL; |
| 410 | + |
| 411 | + result[0] = '\0'; |
| 412 | + for (size_t i = 0; i < count; ++i) { |
| 413 | + strcat(result, strings[i]); |
| 414 | + if (i != count - 1) { |
| 415 | + size_t len = strlen(result); |
| 416 | + result[len] = delimiter; |
| 417 | + result[len + 1] = '\0'; |
| 418 | + } |
| 419 | + } |
| 420 | + |
| 421 | + return result; |
| 422 | +} |
| 423 | + |
| 424 | +int fossil_io_cstring_index_of(ccstring str, ccstring substr) { |
| 425 | + if (!str || !substr) return -1; |
| 426 | + const char *found = strstr(str, substr); |
| 427 | + return found ? (int)(found - str) : -1; |
| 428 | +} |
| 429 | + |
| 430 | +int fossil_io_cstring_equals(ccstring a, ccstring b) { |
| 431 | + return strcmp(a, b) == 0; |
| 432 | +} |
| 433 | + |
| 434 | +int fossil_io_cstring_iequals(ccstring a, ccstring b) { |
| 435 | + if (!a || !b) return 0; |
| 436 | + while (*a && *b) { |
| 437 | + if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0; |
| 438 | + a++; b++; |
| 439 | + } |
| 440 | + return *a == *b; |
| 441 | +} |
| 442 | + |
| 443 | +cstring fossil_io_cstring_escape_json(ccstring str) { |
| 444 | + if (!str) return NULL; |
| 445 | + |
| 446 | + size_t len = strlen(str); |
| 447 | + size_t est = len * 2 + 1; |
| 448 | + char *escaped = malloc(est); |
| 449 | + if (!escaped) return NULL; |
| 450 | + |
| 451 | + char *dst = escaped; |
| 452 | + for (const char *src = str; *src; ++src) { |
| 453 | + switch (*src) { |
| 454 | + case '\"': *dst++ = '\\'; *dst++ = '\"'; break; |
| 455 | + case '\\': *dst++ = '\\'; *dst++ = '\\'; break; |
| 456 | + case '\n': *dst++ = '\\'; *dst++ = 'n'; break; |
| 457 | + case '\r': *dst++ = '\\'; *dst++ = 'r'; break; |
| 458 | + case '\t': *dst++ = '\\'; *dst++ = 't'; break; |
| 459 | + case '\b': *dst++ = '\\'; *dst++ = 'b'; break; |
| 460 | + case '\f': *dst++ = '\\'; *dst++ = 'f'; break; |
| 461 | + default: |
| 462 | + *dst++ = *src; |
| 463 | + } |
| 464 | + } |
| 465 | + *dst = '\0'; |
| 466 | + return escaped; |
| 467 | +} |
| 468 | + |
| 469 | +cstring fossil_io_cstring_unescape_json(ccstring str) { |
| 470 | + if (!str) return NULL; |
| 471 | + |
| 472 | + char *unescaped = malloc(strlen(str) + 1); |
| 473 | + if (!unescaped) return NULL; |
| 474 | + |
| 475 | + char *dst = unescaped; |
| 476 | + for (const char *src = str; *src; ++src) { |
| 477 | + if (*src == '\\') { |
| 478 | + ++src; |
| 479 | + switch (*src) { |
| 480 | + case 'n': *dst++ = '\n'; break; |
| 481 | + case 'r': *dst++ = '\r'; break; |
| 482 | + case 't': *dst++ = '\t'; break; |
| 483 | + case 'b': *dst++ = '\b'; break; |
| 484 | + case 'f': *dst++ = '\f'; break; |
| 485 | + case '\\': *dst++ = '\\'; break; |
| 486 | + case '\"': *dst++ = '\"'; break; |
| 487 | + default: *dst++ = *src; break; |
| 488 | + } |
| 489 | + } else { |
| 490 | + *dst++ = *src; |
| 491 | + } |
| 492 | + } |
| 493 | + *dst = '\0'; |
| 494 | + return unescaped; |
| 495 | +} |
| 496 | + |
| 497 | +cstring fossil_io_cstring_normalize_spaces(cstring str) { |
| 498 | + if (!str) return NULL; |
| 499 | + |
| 500 | + size_t len = strlen(str); |
| 501 | + char *result = malloc(len + 1); |
| 502 | + if (!result) return NULL; |
| 503 | + |
| 504 | + int in_space = 0; |
| 505 | + char *dst = result; |
| 506 | + const char *src = str; |
| 507 | + |
| 508 | + // Skip leading spaces |
| 509 | + while (*src && isspace((unsigned char)*src)) { |
| 510 | + src++; |
| 511 | + } |
| 512 | + |
| 513 | + for (; *src; ++src) { |
| 514 | + if (isspace((unsigned char)*src)) { |
| 515 | + if (!in_space) { |
| 516 | + *dst++ = ' '; |
| 517 | + in_space = 1; |
| 518 | + } |
| 519 | + } else { |
| 520 | + *dst++ = *src; |
| 521 | + in_space = 0; |
| 522 | + } |
| 523 | + } |
| 524 | + |
| 525 | + // Trim trailing space |
| 526 | + if (dst > result && dst[-1] == ' ') dst--; |
| 527 | + *dst = '\0'; |
| 528 | + |
| 529 | + return result; |
| 530 | +} |
| 531 | + |
| 532 | +cstring fossil_io_cstring_strip_quotes(ccstring str) { |
| 533 | + if (!str) return NULL; |
| 534 | + size_t len = strlen(str); |
| 535 | + if (len < 2) return fossil_io_cstring_dup(str); |
| 536 | + |
| 537 | + if ((str[0] == '\'' && str[len - 1] == '\'') || |
| 538 | + (str[0] == '"' && str[len - 1] == '"')) { |
| 539 | + char *result = malloc(len - 1); // len - 2 + 1 |
| 540 | + if (!result) return NULL; |
| 541 | + memcpy(result, str + 1, len - 2); |
| 542 | + result[len - 2] = '\0'; |
| 543 | + return result; |
| 544 | + } |
| 545 | + |
| 546 | + return fossil_io_cstring_dup(str); |
| 547 | +} |
| 548 | + |
| 549 | +cstring fossil_io_cstring_append(cstring *dest, ccstring src) { |
| 550 | + if (!dest || !src) return NULL; |
| 551 | + |
| 552 | + size_t old_len = *dest ? strlen(*dest) : 0; |
| 553 | + size_t add_len = strlen(src); |
| 554 | + |
| 555 | + char *new_str = realloc(*dest, old_len + add_len + 1); |
| 556 | + if (!new_str) return NULL; |
| 557 | + |
| 558 | + memcpy(new_str + old_len, src, add_len + 1); // includes null terminator |
| 559 | + *dest = new_str; |
| 560 | + return new_str; |
| 561 | +} |
| 562 | + |
359 | 563 | // ============================================================================
|
360 | 564 | // String Stream Functions
|
361 | 565 | // ============================================================================
|
|
0 commit comments