From a648b9d829a2d45236be59df0dc95d3d9b2d8d45 Mon Sep 17 00:00:00 2001 From: Aleksey Dobrunov Date: Wed, 16 Oct 2024 21:09:25 +0500 Subject: [PATCH] add test on XmlInputSource; refactoring --- src/colorer/parsers/ParserFactoryImpl.cpp | 2 +- src/colorer/utils/Environment.cpp | 87 ++++++---- src/colorer/utils/Environment.h | 11 +- src/colorer/xml/XmlInputSource.cpp | 2 +- src/colorer/xml/XmlInputSource.h | 2 +- src/colorer/xml/libxml2/LibXmlInputSource.cpp | 11 +- src/colorer/xml/libxml2/LibXmlInputSource.h | 6 +- src/colorer/xml/libxml2/LibXmlReader.cpp | 2 +- .../xml/libxml2/SharedXmlInputSource.cpp | 2 +- tests/unit/CMakeLists.txt | 4 +- tests/unit/test_xmlinputsource.cpp | 158 ++++++++++++++++++ 11 files changed, 239 insertions(+), 48 deletions(-) create mode 100644 tests/unit/test_xmlinputsource.cpp diff --git a/src/colorer/parsers/ParserFactoryImpl.cpp b/src/colorer/parsers/ParserFactoryImpl.cpp index 3fc66dd..f1d57b3 100644 --- a/src/colorer/parsers/ParserFactoryImpl.cpp +++ b/src/colorer/parsers/ParserFactoryImpl.cpp @@ -44,7 +44,7 @@ void ParserFactory::Impl::loadHrcPath(const UnicodeString& location) { try { COLORER_LOG_DEBUG("try load '%'", location); - if (XmlInputSource::isFileURI(*base_catalog_path, &location)) { + if (XmlInputSource::isFsURI(*base_catalog_path, &location)) { auto files = colorer::Environment::getFilesFromPath(base_catalog_path.get(), &location, ".hrc"); for (auto& file : files) { loadHrc(file, nullptr); diff --git a/src/colorer/utils/Environment.cpp b/src/colorer/utils/Environment.cpp index de90164..700d33c 100644 --- a/src/colorer/utils/Environment.cpp +++ b/src/colorer/utils/Environment.cpp @@ -1,11 +1,11 @@ #include "colorer/utils/Environment.h" -#include #ifdef WIN32 #include #include #endif namespace colorer { + fs::path Environment::to_filepath(const UnicodeString* str) { #ifdef _WINDOWS @@ -18,7 +18,7 @@ fs::path Environment::to_filepath(const UnicodeString* str) uUnicodeString Environment::getOSVariable(const UnicodeString& name) { -#ifdef WIN32 +#ifdef _WINDOWS COLORER_LOG_DEBUG("get system environment '%'", name); auto str_name = UStr::to_stdwstr(&name); size_t sz = 0; @@ -44,36 +44,9 @@ uUnicodeString Environment::getOSVariable(const UnicodeString& name) COLORER_LOG_DEBUG("'%' not set", name); return nullptr; } - else { - COLORER_LOG_DEBUG("'%' = '%'", name, value); - return std::make_unique(value); - } -#endif -} - -uUnicodeString Environment::expandEnvironment(const UnicodeString* path) -{ - COLORER_LOG_DEBUG("expand system environment for '%'", *path); -#ifdef WIN32 - std::wstring path_ws = UStr::to_stdwstr(path); - size_t i = ExpandEnvironmentStringsW(path_ws.c_str(), nullptr, 0); - auto temp = std::make_unique(i); - ExpandEnvironmentStringsW(path_ws.c_str(), temp.get(), static_cast(i)); - return std::make_unique(temp.get()); -#else - std::smatch matcher; - std::string result; - auto text = UStr::to_stdstr(path); - static const std::regex env_re {R"--(\$\{([^}]+)\})--"}; - while (std::regex_search(text, matcher, env_re)) { - result += matcher.prefix().str(); - result += std::getenv(matcher[1].str().c_str()); - text = matcher.suffix().str(); - } - result += text; - COLORER_LOG_DEBUG("result of expand '%'", result); - return std::make_unique(result.c_str()); + COLORER_LOG_DEBUG("'%' = '%'", name, value); + return std::make_unique(value); #endif } @@ -84,8 +57,8 @@ uUnicodeString Environment::normalizePath(const UnicodeString* path) fs::path Environment::normalizeFsPath(const UnicodeString* path) { - auto expanded_string = Environment::expandEnvironment(path); - auto fpath = fs::path(Environment::to_filepath(expanded_string.get())); + auto expanded_string = expandEnvironment(*path); + auto fpath = fs::path(to_filepath(&expanded_string)); fpath = fpath.lexically_normal(); if (fs::is_symlink(fpath)) { fpath = fs::read_symlink(fpath); @@ -178,4 +151,52 @@ UnicodeString Environment::expandSpecialEnvironment(const UnicodeString& path) COLORER_LOG_DEBUG("result of expand '%'", result); return UnicodeString(result.c_str()); } + +UnicodeString Environment::expandEnvironment(const UnicodeString& path) +{ + COLORER_LOG_DEBUG("expand system environment for '%'", path); + if (path.isEmpty()) { + COLORER_LOG_DEBUG("result of expand ''"); + return {}; + } + +#ifdef _WINDOWS + std::wstring path_ws = UStr::to_stdwstr(&path); + size_t i = ExpandEnvironmentStringsW(path_ws.c_str(), nullptr, 0); + auto temp = std::make_unique(i); + ExpandEnvironmentStringsW(path_ws.c_str(), temp.get(), static_cast(i)); + COLORER_LOG_DEBUG("result of expand '%'", temp.get()); + return {temp.get()}; +#else + const auto text = UStr::to_stdstr(&path); + auto res = expandEnvByRegexp(text, std::regex(R"--(\$\{([[:alpha:]]\w*)\})--")); + res = expandEnvByRegexp(res, std::regex(R"--(\$([[:alpha:]]\w*)\b)--")); + COLORER_LOG_DEBUG("result of expand '%'", res); + return {res.c_str()}; +#endif +} + +std::string Environment::expandEnvByRegexp(const std::string& path, const std::regex& regex) +{ + std::smatch matcher; + std::string result; + auto text = path; + while (std::regex_search(text, matcher, regex)) { + result += matcher.prefix().str(); + auto env_value = getOSVariable(matcher[1].str().c_str()); + if (env_value) { + // add expanded value + result += UStr::to_stdstr(env_value); + } + else { + // add variable name + result += matcher[0].str(); + } + text = matcher.suffix().str(); + } + result += text; + + return result; +} + } // namespace colorer \ No newline at end of file diff --git a/src/colorer/utils/Environment.h b/src/colorer/utils/Environment.h index 74de02e..a196fcf 100644 --- a/src/colorer/utils/Environment.h +++ b/src/colorer/utils/Environment.h @@ -1,7 +1,10 @@ #ifndef COLORER_ENVIRONMENT_H #define COLORER_ENVIRONMENT_H +#include +#include #include "colorer/Common.h" + #ifdef COLORER_FEATURE_OLD_COMPILERS #include "colorer/platform/filesystem.hpp" namespace fs = ghc::filesystem; @@ -9,14 +12,14 @@ namespace fs = ghc::filesystem; #include namespace fs = std::filesystem; #endif -#include + + namespace colorer { class Environment { public: static uUnicodeString getOSVariable(const UnicodeString& name); - static uUnicodeString expandEnvironment(const UnicodeString* path); static uUnicodeString normalizePath(const UnicodeString* path); static fs::path normalizeFsPath(const UnicodeString* path); static fs::path getClearFilePath(const UnicodeString* basePath, const UnicodeString* relPath); @@ -28,6 +31,10 @@ class Environment static UnicodeString getAbsolutePath(const UnicodeString& basePath, const UnicodeString& relPath); static UnicodeString expandSpecialEnvironment(const UnicodeString& path); + static UnicodeString expandEnvironment(const UnicodeString& path); + +private: + static std::string expandEnvByRegexp(const std::string& path, const std::regex& regex); }; } // namespace colorer diff --git a/src/colorer/xml/XmlInputSource.cpp b/src/colorer/xml/XmlInputSource.cpp index 14f2fbb..824f318 100644 --- a/src/colorer/xml/XmlInputSource.cpp +++ b/src/colorer/xml/XmlInputSource.cpp @@ -18,7 +18,7 @@ UnicodeString& XmlInputSource::getPath() const return xml_input_source->getPath(); } -bool XmlInputSource::isFileURI(const UnicodeString& path, const UnicodeString* base) +bool XmlInputSource::isFsURI(const UnicodeString& path, const UnicodeString* base) { const UnicodeString jar(u"jar:"); if (path.startsWith(jar) || (base && base->startsWith(jar))) { diff --git a/src/colorer/xml/XmlInputSource.h b/src/colorer/xml/XmlInputSource.h index 74773c0..5cd619d 100644 --- a/src/colorer/xml/XmlInputSource.h +++ b/src/colorer/xml/XmlInputSource.h @@ -21,7 +21,7 @@ class XmlInputSource [[nodiscard]] UnicodeString& getPath() const; - static bool isFileURI(const UnicodeString& path, const UnicodeString* base); + static bool isFsURI(const UnicodeString& path, const UnicodeString* base); private: std::unique_ptr xml_input_source; diff --git a/src/colorer/xml/libxml2/LibXmlInputSource.cpp b/src/colorer/xml/libxml2/LibXmlInputSource.cpp index 120d549..d9ba7d1 100644 --- a/src/colorer/xml/libxml2/LibXmlInputSource.cpp +++ b/src/colorer/xml/libxml2/LibXmlInputSource.cpp @@ -5,7 +5,7 @@ LibXmlInputSource::LibXmlInputSource(const UnicodeString& path, const UnicodeString* base) { if (path.isEmpty()) { - throw InputSourceException("XmlInputSource::newInstance: path is empty"); + throw InputSourceException("LibXmlInputSource: path is empty"); } UnicodeString full_path; if (path.startsWith(jar) || (base != nullptr && base->startsWith(jar))) { @@ -43,15 +43,16 @@ UnicodeString& LibXmlInputSource::getPath() } #ifdef COLORER_FEATURE_ZIPINPUTSOURCE + void LibXmlInputSource::initZipSource(const UnicodeString& path, const UnicodeString* base) { - auto paths = getFullPathFromPathJar(path, base); + const auto paths = getFullPathsToZip(path, base); sourcePath = paths.full_path; zip_source = SharedXmlInputSource::getSharedInputSource(paths.path_to_jar); } -PathInJar LibXmlInputSource::getFullPathFromPathJar(const UnicodeString& path, const UnicodeString* base) +PathInJar LibXmlInputSource::getFullPathsToZip(const UnicodeString& path, const UnicodeString* base) { if (path.startsWith(jar)) { const auto path_idx = path.lastIndexOf('!'); @@ -66,7 +67,7 @@ PathInJar LibXmlInputSource::getFullPathFromPathJar(const UnicodeString& path, c const UnicodeString full_path = jar + path_to_jar + u"!" + path_in_jar; return {full_path, path_to_jar, path_in_jar}; } - else { + if (base != nullptr && base->startsWith(jar)) { const auto base_idx = base->lastIndexOf('!'); if (base_idx == -1) { throw InputSourceException("Bad jar uri format: " + path); @@ -77,5 +78,7 @@ PathInJar LibXmlInputSource::getFullPathFromPathJar(const UnicodeString& path, c const UnicodeString full_path = jar + path_to_jar + u"!" + path_in_jar; return {full_path, path_to_jar, path_in_jar}; } + throw InputSourceException("The path to the jar was not found"); } + #endif diff --git a/src/colorer/xml/libxml2/LibXmlInputSource.h b/src/colorer/xml/libxml2/LibXmlInputSource.h index b5e660c..ab35665 100644 --- a/src/colorer/xml/libxml2/LibXmlInputSource.h +++ b/src/colorer/xml/libxml2/LibXmlInputSource.h @@ -21,6 +21,7 @@ class LibXmlInputSource explicit LibXmlInputSource(const UnicodeString& path, const UnicodeString* base = nullptr); ~LibXmlInputSource(); + [[nodiscard]] LibXmlInputSource createRelative(const UnicodeString& relPath) const; [[nodiscard]] @@ -31,12 +32,11 @@ class LibXmlInputSource #ifdef COLORER_FEATURE_ZIPINPUTSOURCE public: - void initZipSource(const UnicodeString& path, const UnicodeString* base = nullptr); - - static PathInJar getFullPathFromPathJar(const UnicodeString& path, const UnicodeString* base); + static PathInJar getFullPathsToZip(const UnicodeString& path, const UnicodeString* base = nullptr); private: SharedXmlInputSource* zip_source {nullptr}; + void initZipSource(const UnicodeString& path, const UnicodeString* base = nullptr); #endif }; diff --git a/src/colorer/xml/libxml2/LibXmlReader.cpp b/src/colorer/xml/libxml2/LibXmlReader.cpp index 4367784..9d15bd2 100644 --- a/src/colorer/xml/libxml2/LibXmlReader.cpp +++ b/src/colorer/xml/libxml2/LibXmlReader.cpp @@ -152,7 +152,7 @@ xmlParserInputPtr LibXmlReader::xmlMyExternalEntityLoader(const char* URL, const if (!is_full_path) { string_url = colorer::Environment::expandSpecialEnvironment(string_url); } - auto paths = LibXmlInputSource::getFullPathFromPathJar(string_url, is_full_path ? nullptr : current_file.get()); + auto paths = LibXmlInputSource::getFullPathsToZip(string_url, is_full_path ? nullptr : current_file.get()); is_full_path = false; xmlParserInputPtr ret = nullptr; try { diff --git a/src/colorer/xml/libxml2/SharedXmlInputSource.cpp b/src/colorer/xml/libxml2/SharedXmlInputSource.cpp index f460a91..35afee6 100644 --- a/src/colorer/xml/libxml2/SharedXmlInputSource.cpp +++ b/src/colorer/xml/libxml2/SharedXmlInputSource.cpp @@ -52,7 +52,7 @@ SharedXmlInputSource::SharedXmlInputSource(const UnicodeString& path) SharedXmlInputSource::~SharedXmlInputSource() { - // не нужно удалять объект, удаляемый из массива. мы и так уже в деструкторе + // You don't need to delete an object that has been deleted from the array. We are already in the destructor. isHash->erase(source_path); if (isHash->empty()) { delete isHash; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index d45e240..aec2478 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -5,7 +5,9 @@ set(unit_tests_SRC test_exception.cpp test_filetype.cpp test_environment.cpp - test_hrcparsing.cpp) + test_hrcparsing.cpp + test_xmlinputsource.cpp +) add_executable(unit_tests ${unit_tests_SRC}) diff --git a/tests/unit/test_xmlinputsource.cpp b/tests/unit/test_xmlinputsource.cpp new file mode 100644 index 0000000..291d2da --- /dev/null +++ b/tests/unit/test_xmlinputsource.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include "colorer/Common.h" +#include "colorer/xml/XmlInputSource.h" + +namespace fs = std::filesystem; + +TEST_CASE("Test create XmlInputSource") +{ + auto work_dir = fs::current_path(); + const UnicodeString empty_string(u""); + + SECTION("Create XmlInputSource with not exists file") + { + REQUIRE_THROWS_WITH(XmlInputSource(empty_string), Catch::Contains("path is empty")); + REQUIRE_THROWS_WITH(XmlInputSource(empty_string, nullptr), Catch::Contains("path is empty")); + REQUIRE_THROWS_WITH(XmlInputSource(empty_string, &empty_string), Catch::Contains("path is empty")); + + auto non_exist_file = work_dir / "non_exist"; + REQUIRE_THROWS_WITH(XmlInputSource(non_exist_file.c_str()), Catch::Contains("isn't regular file")); + REQUIRE_THROWS_WITH(XmlInputSource(non_exist_file.c_str(), nullptr), Catch::Contains("isn't regular file")); + REQUIRE_THROWS_WITH(XmlInputSource(non_exist_file.c_str(), &empty_string), Catch::Contains("isn't regular file")); + } + + // create files for tests + auto temp_path = work_dir / "temporary"; + fs::create_directories(temp_path); + auto test_file1 = temp_path / "test1.xml"; + auto test_file2 = temp_path / "test2.xml"; + std::ofstream(test_file1.c_str()).close(); + std::ofstream(test_file2.c_str()).close(); + + SECTION("Create XmlInputSource with exists file") + { + REQUIRE_NOTHROW(XmlInputSource(test_file1.c_str())); + REQUIRE_NOTHROW(XmlInputSource(test_file1.c_str(), nullptr)); + REQUIRE_NOTHROW(XmlInputSource(test_file1.c_str(), &empty_string)); + } + + SECTION("Create relative XmlInputSource with exists file") + { + auto u_test_file2 = UnicodeString(test_file2.c_str()); + std::unique_ptr test_source; + + REQUIRE_NOTHROW(test_source = std::make_unique("test1.xml", &u_test_file2)); + REQUIRE_NOTHROW(test_source = test_source->createRelative(test_file2.c_str())); + REQUIRE(test_source->getPath().compare(u_test_file2) == 0); + } + + SECTION("Create relative XmlInputSource with not exists file") + { + auto u_test_file2 = UnicodeString(test_file2.c_str()); + std::unique_ptr test_source; + + REQUIRE_NOTHROW(test_source = std::make_unique("test1.xml", &u_test_file2)); + REQUIRE_THROWS_WITH(test_source->createRelative("test3.xml"), Catch::Contains("isn't regular file")); + } + + SECTION("Create XmlInputSource with environment variable in path") + { +#ifndef WIN32 + setenv("COLORER_TEST_XML1", temp_path.c_str(), 1); + UnicodeString path1(R"($COLORER_TEST_XML1/test1.xml)"); + UnicodeString path2(R"($COLORER_TEST_XML2/test2.xml)"); + + REQUIRE_NOTHROW(XmlInputSource(path1)); + REQUIRE_THROWS_WITH(XmlInputSource(path2), Catch::Contains("$COLORER_TEST_XML2/test2.xml isn't regular file")); + REQUIRE_NOTHROW(XmlInputSource("test1.xml", &path1)); +#endif + } + + // clean after tests + std::error_code ec; + fs::remove_all(temp_path, ec); +} + +TEST_CASE("Test isFsURI") +{ + const UnicodeString empty_string(u""); + UnicodeString path1(u"/home/user/testfolder"); + UnicodeString path2(u"/home/user/testfolder2"); + UnicodeString jar_path1(u"jar:common.jar!hrc/1.hrc"); + REQUIRE(XmlInputSource::isFsURI(path1, nullptr) == true); + REQUIRE(XmlInputSource::isFsURI(path1, &path2) == true); + REQUIRE(XmlInputSource::isFsURI(jar_path1, nullptr) == false); + REQUIRE(XmlInputSource::isFsURI(jar_path1, &path2) == false); +} + +#ifndef COLORER_FEATURE_ZIPINPUTSOURCE +TEST_CASE("Work with path to zip: zip disabled") +{ + UnicodeString path2(u"hrc/1.hrc"); + UnicodeString jar_path1(u"jar:colorer.jar!hrc/1.hrc"); + REQUIRE_THROWS_WITH(XmlInputSource(jar_path1), Catch::Contains("zip input source not supported")); + REQUIRE_THROWS_WITH(XmlInputSource(jar_path1, nullptr), Catch::Contains("zip input source not supported")); + REQUIRE_THROWS_WITH(XmlInputSource(path2, &jar_path1), Catch::Contains("zip input source not supported")); +} +#endif + +#ifdef COLORER_FEATURE_ZIPINPUTSOURCE +TEST_CASE("Check expand paths to files from jar-URI") +{ + UnicodeString path_to_c(u"jar:common.zip!base/c.hrc"); + auto paths_cpp = LibXmlInputSource::getFullPathsToZip(path_to_c); + + REQUIRE(paths_cpp.full_path == path_to_c); + REQUIRE(paths_cpp.path_in_jar == u"base/c.hrc"); + REQUIRE(paths_cpp.path_to_jar == u"common.zip"); + + UnicodeString base_path(u"/home/user/base/hrc/proto.hrc"); + UnicodeString full_path(u"jar:/home/user/base/hrc/common.zip!base/c.hrc"); + auto paths_cpp2 = LibXmlInputSource::getFullPathsToZip(path_to_c, &base_path); + + REQUIRE(paths_cpp2.full_path == full_path); + REQUIRE(paths_cpp2.path_in_jar == u"base/c.hrc"); + REQUIRE(paths_cpp2.path_to_jar == u"/home/user/base/hrc/common.zip"); + + auto paths_cpp3 = LibXmlInputSource::getFullPathsToZip(u"c-unix.ent.hrc", &full_path); + REQUIRE(paths_cpp3.full_path == u"jar:/home/user/base/hrc/common.zip!base/c-unix.ent.hrc"); + REQUIRE(paths_cpp3.path_in_jar == u"base/c-unix.ent.hrc"); + REQUIRE(paths_cpp3.path_to_jar == u"/home/user/base/hrc/common.zip"); + + UnicodeString bad_path_to_cpp(u"jar:common.zip/base/c.hrc"); + UnicodeString bad_full_path(u"jar:/home/user/base/hrc/common.zip/base/c.hrc"); + REQUIRE_THROWS_WITH(LibXmlInputSource::getFullPathsToZip(bad_path_to_cpp), Catch::Contains("Bad jar uri format")); + REQUIRE_THROWS_WITH(LibXmlInputSource::getFullPathsToZip("c-unix.ent.hrc", &bad_full_path), + Catch::Contains("Bad jar uri format")); + + REQUIRE_THROWS_WITH(LibXmlInputSource::getFullPathsToZip(base_path), Catch::Contains("The path to the jar was not found")); + REQUIRE_THROWS_WITH(LibXmlInputSource::getFullPathsToZip("c-unix.ent.hrc", &base_path), Catch::Contains("The path to the jar was not found")); + +} + +TEST_CASE("Work with path to zip: zip enabled") +{ + // create files for tests + auto work_dir = fs::current_path(); + auto temp_path = work_dir / "temporary"; + fs::create_directories(temp_path); + auto test_file1 = temp_path / "test1.zip"; + std::ofstream(test_file1.c_str()).close(); + + UnicodeString path_to_zip = u"jar:" + UnicodeString(test_file1.c_str()) + u"!base/c.hrc"; + UnicodeString badpath_to_zip = u"jar:" + UnicodeString(temp_path.c_str()) + u"test1.zip!base/c.hrc"; + + REQUIRE_NOTHROW(XmlInputSource(path_to_zip)); + REQUIRE_NOTHROW(XmlInputSource(u"c-unix.ent.hrc", &path_to_zip)); + + UnicodeString full_path(u"jar:/home/user/base/hrc/common.zip!base/c.hrc"); + REQUIRE_NOTHROW(XmlInputSource(full_path)); + REQUIRE_NOTHROW(XmlInputSource(u"c-unix.ent.hrc", &full_path)); + + // clean after tests + std::error_code ec; + fs::remove_all(temp_path, ec); +} +#endif \ No newline at end of file