Converts string based on the transform function 'fn'. The unit of the conversion is a wchar_t (i.e. uint32_t) which is parsed from multi bytes using std::mbtowc(). The transform function 'fn' accepts two parameters: the original wchar_t and a flag indicating whether it's the first character of a word. After the transformation, the wchar_t is converted back to bytes.
| 380 | /// indicating whether it's the first character of a word. |
| 381 | /// After the transformation, the wchar_t is converted back to bytes. |
| 382 | static StringVal Utf8CaseConversion(FunctionContext* context, const StringVal& str, |
| 383 | uint32_t (*fn)(uint32_t, bool*)) { |
| 384 | // Usually the upper/lower cases have the same size in bytes. Here we add 4 bytes |
| 385 | // buffer in case of illegal Unicodes. |
| 386 | int max_result_bytes = str.len + 4; |
| 387 | StringVal result(context, max_result_bytes); |
| 388 | if (UNLIKELY(result.is_null)) return StringVal::null(); |
| 389 | wchar_t wc; |
| 390 | int wc_bytes; |
| 391 | bool word_start = true; |
| 392 | uint8_t* result_ptr = result.ptr; |
| 393 | std::mbstate_t wc_state{}; |
| 394 | std::mbstate_t mb_state{}; |
| 395 | for (int i = 0; i < str.len; i += wc_bytes) { |
| 396 | // std::mbtowc converts a multibyte sequence to a wide character. It's not |
| 397 | // thread safe. Here we use std::mbrtowc instead. |
| 398 | wc_bytes = std::mbrtowc(&wc, reinterpret_cast<char*>(str.ptr + i), str.len - i, |
| 399 | &wc_state); |
| 400 | bool needs_conversion = true; |
| 401 | if (wc_bytes == 0) { |
| 402 | // std::mbtowc returns 0 when hitting '\0'. |
| 403 | wc = 0; |
| 404 | wc_bytes = 1; |
| 405 | } else if (wc_bytes < 0) { |
| 406 | ReportErrorBytes(context, str, i); |
| 407 | // Replace it to the replacement character (U+FFFD) |
| 408 | wc = 0xFFFD; |
| 409 | needs_conversion = false; |
| 410 | // Jump to the next legal UTF-8 start byte. |
| 411 | wc_bytes = 1; |
| 412 | while (i + wc_bytes < str.len && !BitUtil::IsUtf8StartByte(str.ptr[i + wc_bytes])) { |
| 413 | wc_bytes++; |
| 414 | } |
| 415 | } |
| 416 | if (needs_conversion) wc = fn(wc, &word_start); |
| 417 | // std::wctomb converts a wide character to a multibyte sequence. It's not |
| 418 | // thread safe. Here we use std::wcrtomb instead. |
| 419 | int res_bytes = std::wcrtomb(reinterpret_cast<char*>(result_ptr), wc, &mb_state); |
| 420 | if (res_bytes <= 0) { |
| 421 | if (needs_conversion) { |
| 422 | context->AddWarning(Substitute( |
| 423 | "Ignored illegal wide character in results: $0. Current locale: $1", |
| 424 | wc, std::locale("").name()).c_str()); |
| 425 | } |
| 426 | continue; |
| 427 | } |
| 428 | result_ptr += res_bytes; |
| 429 | if (result_ptr - result.ptr > max_result_bytes - 4) { |
| 430 | // Double the result buffer for overflow |
| 431 | max_result_bytes *= 2; |
| 432 | max_result_bytes = min<int>(StringVal::MAX_LENGTH, |
| 433 | static_cast<int>(BitUtil::RoundUpToPowerOfTwo(max_result_bytes))); |
| 434 | int offset = result_ptr - result.ptr; |
| 435 | if (UNLIKELY(!result.Resize(context, max_result_bytes))) return StringVal::null(); |
| 436 | result_ptr = result.ptr + offset; |
| 437 | } |
| 438 | } |
| 439 | result.len = result_ptr - result.ptr; |
no test coverage detected