diff --git a/.codecov.yml b/.codecov.yml index 229246c..8778dc5 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,6 +4,7 @@ codecov: ignore: - "deps" - "include/upa/idna.h" + - "include/upa/url_for_*.h" - "src/idna.cpp" - "test" diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index e54f838..6c9117b 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -45,6 +45,13 @@ jobs: cxx_standard: 20 cmake_options: "" + - name: g++ C++17 Qt + os: ubuntu-24.04 + cxx_compiler: g++ + cxx_standard: 17 + cmake_options: "-DUPA_TEST_URL_FOR_QT=ON" + install: "qt6-base-dev" + - name: g++ C++17 Codecov os: ubuntu-latest cxx_compiler: g++ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3748187..3bc6b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ option(UPA_INSTALL "Generate the install target." ON) # library options option(UPA_AMALGAMATED "Use amalgamated URL library source." OFF) # tests build options +option(UPA_TEST_URL_FOR_QT "Build tests with Qt strings" OFF) option(UPA_TEST_COVERAGE "Build tests with code coverage reporting" OFF) option(UPA_TEST_COVERAGE_CLANG "Build tests with Clang source-based code coverage" OFF) option(UPA_TEST_SANITIZER "Build tests with Clang sanitizer" OFF) @@ -77,6 +78,11 @@ if (UPA_BUILD_FUZZER) endif() endif() +# Tests with Qt strings +if (UPA_TEST_URL_FOR_QT) + find_package(Qt6 REQUIRED COMPONENTS Core) +endif() + # Code coverage reporting if (UPA_TEST_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") # add flags for GCC & LLVM/Clang @@ -156,6 +162,7 @@ if (UPA_BUILD_TESTS) test/test-url.cpp test/test-url-port.cpp test/test-url-setters.cpp + test/test-url_for_.cpp test/test-url_host.cpp test/test-url_percent_encode.cpp test/test-url_search_params.cpp @@ -169,7 +176,14 @@ if (UPA_BUILD_TESTS) get_filename_component(test_name ${file} NAME_WE) add_executable(${test_name} ${file}) - target_link_libraries(${test_name} ${upa_lib_target}) + target_link_libraries(${test_name} PRIVATE ${upa_lib_target}) + + if ("${test_name}" STREQUAL "test-url_for_") + if (UPA_TEST_URL_FOR_QT) + target_compile_definitions(${test_name} PRIVATE UPA_TEST_URL_FOR_QT) + target_link_libraries(${test_name} PRIVATE Qt6::Core) + endif() + endif() add_test(NAME ${test_name} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test diff --git a/Doxyfile b/Doxyfile index bfe097d..30b6968 100644 --- a/Doxyfile +++ b/Doxyfile @@ -970,7 +970,8 @@ INPUT = include/upa/url.h \ include/upa/url_result.h \ include/upa/url_search_params.h \ include/upa/url_percent_encode.h \ - README.md + README.md \ + doc/string_input.md # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/README.md b/README.md index cee329e..5b5282f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Upa URL contains features not specified in the standard: 4. Experimental URLHost class (see proposal: https://github.com/whatwg/url/pull/288): `upa::url_host` 5. The `upa::url_search_params` class has a few additional functions: `remove`, `remove_if` -For string input, the library supports UTF-8, UTF-16, UTF-32 encodings and several string types, including `std::basic_string`, `std::basic_string_view`, null-terminated strings of any char type: `char`, `char8_t`, `char16_t`, `char32_t`, or `wchar_t`. +For string input, the library supports UTF-8, UTF-16, UTF-32 encodings and several string types, including `std::basic_string`, `std::basic_string_view`, null-terminated strings of any char type: `char`, `char8_t`, `char16_t`, `char32_t`, or `wchar_t`. See ["String input"](doc/string_input.md) for more information. ## Installation diff --git a/doc/string_input.md b/doc/string_input.md new file mode 100644 index 0000000..322803c --- /dev/null +++ b/doc/string_input.md @@ -0,0 +1,23 @@ +## String input + +For string input, the library supports UTF-8, UTF-16, UTF-32 encodings and several string types, including `std::basic_string`, `std::basic_string_view`, null-terminated strings of any char type: `char`, `char8_t`, `char16_t`, `char32_t`, or `wchar_t`. + +The ATL/MFC and Qt library string types are also supported. To use them you need to include the header files listed in the table below instead of `url.h`: + +| Library | Supported string types | Include instead of `url.h` | +|-|-|-| +| ATL/MFC | `CSimpleStringT`, `CStringT`, `CFixedStringT` | `url_for_atl.h` | +| Qt | `QString`, `QStringView`, `QUtf8StringView` | `url_for_qt.h` | + +Qt example: +```cpp +#include "upa/url_for_qt.h" +#include + +int main() { + QString str{"http://example.com/"}; + upa::url url{str}; + std::cout << url.href() << '\n'; + return 0; +} +``` diff --git a/include/upa/url_for_atl.h b/include/upa/url_for_atl.h new file mode 100644 index 0000000..979e0a9 --- /dev/null +++ b/include/upa/url_for_atl.h @@ -0,0 +1,63 @@ +// Copyright 2024 Rimas Misevičius +// Distributed under the BSD-style license that can be +// found in the LICENSE file. + +// This file can be included instead of url.h to allow strings of the +// ATL/MFC classes CSimpleStringT, CStringT, CFixedStringT to be used +// as string input in the functions of this library. +// +#ifndef UPA_URL_FOR_ATL_H +#define UPA_URL_FOR_ATL_H + +#include "config.h" +#include "url.h" // IWYU pragma: export +#include +#ifdef UPA_CPP_20 +# include +#else +# include +#endif + +namespace upa { + +template +struct str_arg_char_for_atl { + using type = typename StrT::XCHAR; + + static str_arg to_str_arg(const StrT& str) { + return { str.GetString(), static_cast(str.GetLength()) }; + } +}; + +#ifdef UPA_CPP_20 + +// CStringT and CFixedStringT are derived from CSimpleStringT +template +concept derived_from_CSimpleString = + std::derived_from> || + std::derived_from> || + std::derived_from> || + std::derived_from>; + +template +struct str_arg_char : public str_arg_char_for_atl {}; + +#else // UPA_CPP_20 + +template +struct str_arg_char> : + public str_arg_char_for_atl> {}; + +template +struct str_arg_char> : + public str_arg_char_for_atl> {}; + +template +struct str_arg_char> : + public str_arg_char_for_atl> {}; + +#endif // UPA_CPP_20 + +} // namespace upa + +#endif // UPA_URL_FOR_ATL_H diff --git a/include/upa/url_for_qt.h b/include/upa/url_for_qt.h new file mode 100644 index 0000000..36d2eb1 --- /dev/null +++ b/include/upa/url_for_qt.h @@ -0,0 +1,61 @@ +// Copyright 2024 Rimas Misevičius +// Distributed under the BSD-style license that can be +// found in the LICENSE file. + +// This file can be included instead of url.h to allow strings of the +// Qt classes QString, QStringView, QUtf8StringView to be used as string +// input in the functions of this library. +// +#ifndef UPA_URL_FOR_QT_H +#define UPA_URL_FOR_QT_H + +#include "url.h" // IWYU pragma: export +#include +#if defined(__has_include) && __has_include() +# include +#else +# include +#endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +# include +#endif +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +# include +#endif +#include // std::ptrdiff_t + +namespace upa { + +template +struct str_arg_char_for_qt { + static_assert(sizeof(typename StrT::value_type) == sizeof(CharT), + "StrT::value_type and CharT must be the same size"); + using type = CharT; + + static str_arg to_str_arg(const StrT& str) { + return { + reinterpret_cast(str.data()), + static_cast(str.length()) + }; + } +}; + +template<> +struct str_arg_char : + public str_arg_char_for_qt {}; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +template<> +struct str_arg_char : + public str_arg_char_for_qt {}; +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +template<> +struct str_arg_char : + public str_arg_char_for_qt {}; +#endif + +} // namespace upa + +#endif // UPA_URL_FOR_QT_H diff --git a/test/test-url_for_.cpp b/test/test-url_for_.cpp new file mode 100644 index 0000000..149a62b --- /dev/null +++ b/test/test-url_for_.cpp @@ -0,0 +1,85 @@ +// Copyright 2024 Rimas Misevičius +// Distributed under the BSD-style license that can be +// found in the LICENSE file. +// + +#include "doctest-main.h" + +#ifdef _MSC_VER +# define UPA_TEST_URL_FOR_ATL +# include "upa/url_for_atl.h" +# include +# include +# include +#endif + +#ifdef UPA_TEST_URL_FOR_QT +# include "upa/url_for_qt.h" +# include +# if defined(__has_include) && __has_include() +# include +# else +# include +# endif +# if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +# include +# endif +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +# include +# endif +#endif + + +#ifdef UPA_TEST_URL_FOR_ATL + +TEST_CASE("ATL::CSimpleStringA input") { + ATL::CStringA basestr; + + ATL::CSimpleStringA str_input("http://host/", basestr.GetManager()); + upa::url url{ str_input }; + CHECK(url.href() == "http://host/"); +} + +TEST_CASE("ATL::CSimpleStringW input") { + ATL::CStringW basestr; + + ATL::CSimpleStringW str_input(L"http://host/", basestr.GetManager()); + upa::url url{ str_input }; + CHECK(url.href() == "http://host/"); +} + +TEST_CASE_TEMPLATE_DEFINE("ATL string input", StrT, test_url_for_atl) { + StrT str_input("http://host/"); + upa::url url{ str_input }; + CHECK(url.href() == "http://host/"); +} + +TEST_CASE_TEMPLATE_INVOKE(test_url_for_atl, + ATL::CStringA, ATL::CFixedStringT, + ATL::CStringW, ATL::CFixedStringT); + +#endif // UPA_TEST_URL_FOR_ATL + +#ifdef UPA_TEST_URL_FOR_QT + +TEST_CASE_TEMPLATE_DEFINE("Qt string input", StrT, test_url_for_qt) { + StrT str_input("http://host/"); + upa::url url{ str_input }; + CHECK(url.href() == "http://host/"); +} + +TEST_CASE_TEMPLATE_INVOKE(test_url_for_qt, QString); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +TEST_CASE("QStringView input") { + QStringView str_input(u"http://host/"); + upa::url url{ str_input }; + CHECK(url.href() == "http://host/"); +} +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +TEST_CASE_TEMPLATE_INVOKE(test_url_for_qt, QUtf8StringView); +#endif + +#endif // UPA_TEST_URL_FOR_QT