From 052f8d2bd27498abc0900dd2b0c7a297c2784b05 Mon Sep 17 00:00:00 2001 From: Martchus Date: Thu, 17 Mar 2022 22:31:11 +0100 Subject: [PATCH] Add helper to make native path from internal representaton --- conversion/stringconversion.cpp | 31 +++++++++++++++++++++++++++---- conversion/stringconversion.h | 1 + io/path.h | 33 +++++++++++++++++++++++++++++++++ tests/iotests.cpp | 27 +++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/conversion/stringconversion.cpp b/conversion/stringconversion.cpp index 3cbebb1..6b5086a 100644 --- a/conversion/stringconversion.cpp +++ b/conversion/stringconversion.cpp @@ -193,7 +193,30 @@ StringData convertUtf8ToLatin1(const char *inputBuffer, std::size_t inputBufferS #ifdef PLATFORM_WINDOWS /*! - * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI. + * \remarks Only available under Windows. + */ +std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer) +{ + // calculate required size + auto widePath = std::wstring(); + auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), nullptr, 0); + if (size <= 0) { + ec = std::error_code(GetLastError(), std::system_category()); + return widePath; + } + // do the actual conversion + widePath.resize(static_cast(size)); + size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), widePath.data(), size); + if (size <= 0) { + ec = std::error_code(GetLastError(), std::system_category()); + widePath.clear(); + } + return widePath; +} + +/*! + * \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI. * \remarks * - Only available under Windows. * - If \a inputBufferSize is -1, \a inputBuffer is considered null-terminated. @@ -218,7 +241,7 @@ WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuff } /*! - * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI. * \remarks Only available under Windows. */ WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer) @@ -228,7 +251,7 @@ WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &in } /*! - * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI. * \remarks * - Only available under Windows. * - If \a inputBufferSize is -1, \a inputBuffer is considered null-terminated. @@ -240,7 +263,7 @@ WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSi } /*! - * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI. * \remarks Only available under Windows. */ WideStringData convertMultiByteToWide(const std::string &inputBuffer) diff --git a/conversion/stringconversion.h b/conversion/stringconversion.h index 8d72d72..651b80e 100644 --- a/conversion/stringconversion.h +++ b/conversion/stringconversion.h @@ -58,6 +58,7 @@ CPP_UTILITIES_EXPORT StringData convertUtf8ToLatin1(const char *inputBuffer, std #ifdef PLATFORM_WINDOWS using WideStringData = std::pair, int>; +CPP_UTILITIES_EXPORT std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer); CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuffer, int inputBufferSize = -1); CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer); CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSize = -1); diff --git a/io/path.h b/io/path.h index 8554af4..f739382 100644 --- a/io/path.h +++ b/io/path.h @@ -3,8 +3,13 @@ #include "../global.h" +#ifdef PLATFORM_WINDOWS +#include "../conversion/stringconversion.h" +#endif + #include #include +#include #ifdef PLATFORM_WINDOWS #define PATH_SEP_CHAR '\\' @@ -27,6 +32,34 @@ CPP_UTILITIES_EXPORT std::string_view fileName(std::string_view path); CPP_UTILITIES_EXPORT std::string_view directory(std::string_view path); #endif CPP_UTILITIES_EXPORT void removeInvalidChars(std::string &fileName); + +/*! + * \brief Returns \a path in the platform's native encoding. + * \remarks + * - On Windows we store paths internally as UTF-8 strings. So it is assumed that \a path is UTF-8 and a UTF-16 + * std::wstring is returned. + * - On any other platforms we store paths internally using the native format (usually UTF-8). So it is assumed + * that \a path is already encoded as intended and passed as-is. + * - This function does basically the same as libstdc++'s `std::filesystem::u8path` implementation. However, the + * C++ standard actually imposes a conversion to UTF-8 when a non-UTF-8 narrow encoding is used. That's not + * wanted here. Besides, that function is deprecated in C++ 20. + */ +inline +#ifdef PLATFORM_WINDOWS + std::wstring +#else + std::string_view +#endif + makeNativePath(std::string_view path) +{ +#ifdef PLATFORM_WINDOWS + auto ec = std::error_code(); + return convertMultiByteToWide(ec, path); +#else + return path; +#endif +} + } // namespace CppUtilities #endif // IOUTILITIES_PATHHELPER_H diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 085712b..c3adb43 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -1,5 +1,23 @@ #include "./testutils.h" +#include "../conversion/stringconversion.h" + +/*! + * \brief Allows printing std::wstring using CPPUNIT_ASSERT_EQUAL. + */ +std::ostream &operator<<(std::ostream &out, const std::wstring &s) +{ + const auto utf8 = CppUtilities:: +#ifdef CONVERSION_UTILITIES_IS_BYTE_ORDER_LITTLE_ENDIAN + convertUtf16LEToUtf8 +#else + convertUtf16BEToUtf8 +#endif + (reinterpret_cast(s.data()), s.size() * (sizeof(std::wstring::value_type) / sizeof(char))); + out.write(utf8.first.get(), utf8.second); + return out; +} + #include "../conversion/conversionexception.h" #include "../conversion/stringbuilder.h" @@ -312,6 +330,15 @@ void IoTests::testPathUtilities() string invalidPath("lib/c++uti*lities.so?"); removeInvalidChars(invalidPath); CPPUNIT_ASSERT(invalidPath == "libc++utilities.so"); + + const auto input = std::string_view("some/path/täst"); +#ifdef PLATFORM_WINDOWS + const auto expected = std::wstring(L"some/path/täst"); +#else + const auto expected = input; +#endif + const auto output = makeNativePath(input); + CPPUNIT_ASSERT_EQUAL_MESSAGE("makeNativePath()", expected, output); } /*!