From a7543cf9ab99521244b34f17b903307cf3fa7dd6 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Mon, 13 Jan 2025 09:54:50 +0100 Subject: [PATCH] string: add snprintf Signed-off-by: Simon Rozman --- UnitTests/main.cpp | 1 + UnitTests/pch.hpp | 1 + UnitTests/string.cpp | 27 +++++++++++++++++++++++++++ include/stdex/sgml.hpp | 8 ++++---- include/stdex/string.hpp | 39 +++++++++++++++++++++++++++++++++++++++ include/stdex/uuid.hpp | 2 +- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/UnitTests/main.cpp b/UnitTests/main.cpp index c1fe4e940..cb9ad24a5 100644 --- a/UnitTests/main.cpp +++ b/UnitTests/main.cpp @@ -28,6 +28,7 @@ int main(int, const char *[]) UnitTests::stream::replicator(); UnitTests::string::strncpy(); UnitTests::string::sprintf(); + UnitTests::string::snprintf(); UnitTests::unicode::charset_encoder(); UnitTests::unicode::normalize(); UnitTests::unicode::str2wstr(); diff --git a/UnitTests/pch.hpp b/UnitTests/pch.hpp index 5837963c9..c9b3e5cfb 100644 --- a/UnitTests/pch.hpp +++ b/UnitTests/pch.hpp @@ -109,6 +109,7 @@ namespace UnitTests public: TEST_METHOD(strncpy); TEST_METHOD(sprintf); + TEST_METHOD(snprintf); }; TEST_CLASS(unicode) diff --git a/UnitTests/string.cpp b/UnitTests/string.cpp index a4e0ff27c..b1d023bbb 100644 --- a/UnitTests/string.cpp +++ b/UnitTests/string.cpp @@ -42,4 +42,31 @@ namespace UnitTests Assert::AreEqual(str.c_str(), stdex::sprintf("%s", locale, str.data()).c_str()); Assert::AreEqual(str.size(), stdex::sprintf("%s", locale, str.data()).size()); } + + void string::snprintf() + { + stdex::locale locale(stdex::create_locale(LC_ALL, "en_US.UTF-8")); + + { + wchar_t buf[0x100]; + Assert::IsTrue(stdex::snprintf(buf, _countof(buf), L"This is %ls.", locale, L"a test") == 15); + Assert::AreEqual(L"This is a test.", buf); + } + { + char buf[0x100]; + Assert::IsTrue(stdex::snprintf(buf, _countof(buf), "This is %s.", locale, "a test") == 15); + Assert::AreEqual("This is a test.", buf); + } + + { + wchar_t buf[8]; + Assert::IsTrue(stdex::snprintf(buf, _countof(buf), L"This is %ls.", locale, L"a test") == 8); + Assert::IsTrue(stdex::strncmp(L"This is a test.", buf, _countof(buf)) == 0); + } + { + char buf[8]; + Assert::IsTrue(stdex::snprintf(buf, _countof(buf), "This is %s.", locale, "a test") == 8); + Assert::IsTrue(stdex::strncmp("This is a test.", buf, _countof(buf)) == 0); + } + } } diff --git a/include/stdex/sgml.hpp b/include/stdex/sgml.hpp index 0ee8aa60f..a16fba8a4 100644 --- a/include/stdex/sgml.hpp +++ b/include/stdex/sgml.hpp @@ -620,7 +620,7 @@ namespace stdex dst.append(1, static_cast(src[i++])); else { char tmp[3 + 8 + 1 + 1]; - snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(src[i++])); + ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(src[i++])); dst.append(tmp); } } @@ -638,7 +638,7 @@ namespace stdex dst.append(1, static_cast(src[i++])); else { char tmp[3 + 8 + 1 + 1]; - snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(wstr_to_utf32(src, i, end))); + ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(wstr_to_utf32(src, i, end))); dst.append(tmp); } } @@ -742,7 +742,7 @@ namespace stdex } else { char tmp[3 + 8 + 1 + 1]; - int m = snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(src[i++])); + int m = ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(src[i++])); stdex_assert(m >= 0); if (static_cast(m) >= count_dst) throw buffer_overrun; @@ -770,7 +770,7 @@ namespace stdex } else { char tmp[3 + 8 + 1 + 1]; - int m = snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(wstr_to_utf32(src, i, end))); + int m = ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast(wstr_to_utf32(src, i, end))); stdex_assert(m >= 0); if (static_cast(m) >= count_dst) throw buffer_overrun; diff --git a/include/stdex/string.hpp b/include/stdex/string.hpp index 9857883b6..243436dcb 100644 --- a/include/stdex/string.hpp +++ b/include/stdex/string.hpp @@ -2655,6 +2655,45 @@ namespace stdex } /// \endcond + /// + /// Formats string using `printf()`. + /// + /// \param[out] str Output string + /// \param[in ] capacity Number of available code units in str + /// \param[in ] format String template using `printf()` style + /// \param[in ] locale Stdlib locale used to perform formatting. Use `NULL` to use locale globally set by `setlocale()`. + /// + /// \return Number of output code units + /// + template + inline size_t snprintf(_Out_z_cap_(capacity) T* str, _In_ size_t capacity, _In_z_ _Printf_format_string_params_(2) const T* format, _In_opt_ locale_t locale, ...) + { + va_list arg; + va_start(arg, locale); + int count = vsnprintf(str, capacity, format, locale, arg); + va_end(arg); + if (0 <= count && static_cast(count) <= capacity) + return static_cast(count); +#ifdef _WIN32 + if (count < 0) { + switch (errno) { + case 0: return capacity; + case EINVAL: throw std::invalid_argument("invalid snprintf arguments"); + case EILSEQ: throw std::runtime_error("encoding error"); + default: throw std::runtime_error("failed to format string"); + } + } else + return capacity; +#else + switch (errno) { + case EOVERFLOW: return capacity; + case EINVAL: throw std::invalid_argument("invalid snprintf arguments"); + case EILSEQ: throw std::runtime_error("encoding error"); + default: throw std::runtime_error("failed to format string"); + } +#endif + } + /// /// Formats string using `printf()`. /// diff --git a/include/stdex/uuid.hpp b/include/stdex/uuid.hpp index 4d213d40e..0b8f74fd5 100644 --- a/include/stdex/uuid.hpp +++ b/include/stdex/uuid.hpp @@ -43,7 +43,7 @@ namespace stdex static_cast(id.Data4[0]), static_cast(id.Data4[1]), static_cast(id.Data4[2]), static_cast(id.Data4[3]), static_cast(id.Data4[4]), static_cast(id.Data4[5]), static_cast(id.Data4[6]), static_cast(id.Data4[7])); #else - snprintf(str, uuid_str_max, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + ::snprintf(str, uuid_str_max, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", *reinterpret_cast(&id[0]), static_cast(*reinterpret_cast(&id[4])), static_cast(*reinterpret_cast(&id[6])),