For a given text, initialize the first letter after a word-separator and lowercase the others e.g: - "IT is a tEXt str" -> "It Is A Text Str"
| 473 | // the others e.g: |
| 474 | // - "IT is a tEXt str" -> "It Is A Text Str" |
| 475 | GANDIVA_EXPORT |
| 476 | const char* gdv_fn_initcap_utf8(int64_t context, const char* data, int32_t data_len, |
| 477 | int32_t* out_len) { |
| 478 | if (data_len == 0) { |
| 479 | *out_len = data_len; |
| 480 | return ""; |
| 481 | } |
| 482 | |
| 483 | // If it is a single-byte character (ASCII), corresponding uppercase is always 1-byte |
| 484 | // long; if it is >= 2 bytes long, uppercase can be at most 4 bytes long, so length of |
| 485 | // the output can be at most twice the length of the input |
| 486 | char* out = reinterpret_cast<char*>(gdv_fn_context_arena_malloc(context, 2 * data_len)); |
| 487 | if (out == nullptr) { |
| 488 | gdv_fn_context_set_error_msg(context, "Could not allocate memory for output string"); |
| 489 | *out_len = 0; |
| 490 | return ""; |
| 491 | } |
| 492 | |
| 493 | int32_t char_len = 0; |
| 494 | int32_t out_char_len = 0; |
| 495 | int32_t out_idx = 0; |
| 496 | uint32_t char_codepoint; |
| 497 | |
| 498 | // Any character is considered as space, except if it is alphanumeric |
| 499 | bool last_char_was_space = true; |
| 500 | |
| 501 | for (int32_t i = 0; i < data_len; i += char_len) { |
| 502 | // An optimization for single byte characters: |
| 503 | if (static_cast<signed char>(data[i]) >= 0) { // 1-byte char (0x00 ~ 0x7F) |
| 504 | char_len = 1; |
| 505 | char cur = data[i]; |
| 506 | |
| 507 | if (cur >= 0x61 && cur <= 0x7a && last_char_was_space) { |
| 508 | // Check if the character is the first one of the word and it is |
| 509 | // lowercase -> 'a' - 'z' : 0x61 - 0x7a. |
| 510 | // Then turn it into uppercase -> 'A' - 'Z' : 0x41 - 0x5a |
| 511 | out[out_idx++] = static_cast<char>(cur - 0x20); |
| 512 | last_char_was_space = false; |
| 513 | } else if (cur >= 0x41 && cur <= 0x5a && !last_char_was_space) { |
| 514 | out[out_idx++] = static_cast<char>(cur + 0x20); |
| 515 | } else { |
| 516 | // Check if the ASCII character is not an alphanumeric character: |
| 517 | // '0' - '9': 0x30 - 0x39 |
| 518 | // 'a' - 'z' : 0x61 - 0x7a |
| 519 | // 'A' - 'Z' : 0x41 - 0x5a |
| 520 | last_char_was_space = (cur < 0x30) || (cur > 0x39 && cur < 0x41) || |
| 521 | (cur > 0x5a && cur < 0x61) || (cur > 0x7a); |
| 522 | out[out_idx++] = cur; |
| 523 | } |
| 524 | continue; |
| 525 | } |
| 526 | |
| 527 | char_len = gdv_fn_utf8_char_length(data[i]); |
| 528 | |
| 529 | // Control reaches here when we encounter a multibyte character |
| 530 | const auto* in_char = (const uint8_t*)(data + i); |
| 531 | |
| 532 | // Decode the multibyte character |