Mercurial > malikania
changeset 185:975dffc6567a
Tests: use ptree directly in mlk-map, closes #704 @1h
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sat, 20 Oct 2018 20:38:43 +0200 |
parents | eaf3cea01010 |
children | 16ff680a8a94 |
files | tests/tools/CMakeLists.txt tests/tools/map/CMakeLists.txt tests/tools/map/main.cpp tools/map/base64.hpp tools/map/main.cpp |
diffstat | 5 files changed, 181 insertions(+), 205 deletions(-) [+] |
line wrap: on
line diff
--- a/tests/tools/CMakeLists.txt Fri Oct 19 20:30:16 2018 +0200 +++ b/tests/tools/CMakeLists.txt Sat Oct 20 20:38:43 2018 +0200 @@ -16,4 +16,5 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # +add_subdirectory(map) add_subdirectory(tileset)
--- a/tests/tools/map/CMakeLists.txt Fri Oct 19 20:30:16 2018 +0200 +++ b/tests/tools/map/CMakeLists.txt Sat Oct 20 20:38:43 2018 +0200 @@ -52,7 +52,7 @@ endforeach() malikania_create_test( - NAME map + NAME mlk-map LIBRARIES json SOURCES ${sources}
--- a/tests/tools/map/main.cpp Fri Oct 19 20:30:16 2018 +0200 +++ b/tests/tools/map/main.cpp Sat Oct 20 20:38:43 2018 +0200 @@ -69,20 +69,22 @@ return result; } -#define TEST_BODY(file, title) \ - auto json = json::parse(std::ifstream(BINARY_DIRECTORY "/" file ".json")); \ - \ - BOOST_REQUIRE_EQUAL(json["title"].get<std::string>(), title); \ - BOOST_REQUIRE_EQUAL(json["tileset"].get<std::string>(), "minimal.png"); \ - BOOST_REQUIRE_EQUAL(json["cell"][0].get<int>(), 32); \ - BOOST_REQUIRE_EQUAL(json["cell"][1].get<int>(), 32); \ - BOOST_REQUIRE_EQUAL(json["size"][0].get<int>(), 10); \ - BOOST_REQUIRE_EQUAL(json["size"][1].get<int>(), 10); \ - \ - auto total = json["size"][0].get<int>() * json["size"][1].get<int>(); \ - auto back = load(BINARY_DIRECTORY "/" file ".map", total, true); \ - \ - BOOST_REQUIRE_EQUAL_COLLECTIONS(back.begin(), back.end(), \ +#define TEST_BODY(file, title) \ + auto path = CMAKE_CURRENT_BINARY_DIR "/" file ".json"; \ + auto json = json::parse(std::ifstream(path)); \ + \ + BOOST_TEST(json["title"].get<std::string>() == title); \ + BOOST_TEST(json["tileset"].get<std::string>() == "minimal.png"); \ + BOOST_TEST(json["cell"][0].get<int>() == 32); \ + BOOST_TEST(json["cell"][1].get<int>() == 32); \ + BOOST_TEST(json["size"][0].get<int>() == 10); \ + BOOST_TEST(json["size"][1].get<int>() == 10); \ + \ + auto map = CMAKE_CURRENT_BINARY_DIR "/" file ".map"; \ + auto total = json["size"][0].get<int>() * json["size"][1].get<int>(); \ + auto back = load(map, total, true); \ + \ + BOOST_REQUIRE_EQUAL_COLLECTIONS(back.begin(), back.end(), \ true_background.begin(), true_background.end()); } // !namespace
--- a/tools/map/base64.hpp Fri Oct 19 20:30:16 2018 +0200 +++ b/tools/map/base64.hpp Sat Oct 20 20:38:43 2018 +0200 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef MALIKANIA_BASE64_HPP -#define MALIKANIA_BASE64_HPP +#ifndef MALIKANIA_MAP_BASE64_HPP +#define MALIKANIA_MAP_BASE64_HPP /** * \file base64.hpp @@ -47,9 +47,9 @@ * * ```` * try { - * std::string text = base64::decode(msg); + * std::string text = base64::decode(msg); * } catch (const std::exception &ex) { - * std::cerr << ex.what() << std::endl; + * std::cerr << ex.what() << std::endl; * } * ```` */ @@ -58,13 +58,12 @@ #include <cctype> #include <stdexcept> #include <string> - -namespace mlk { +#include <string_view> /** * \brief main %base64 namespace. */ -namespace base64 { +namespace mlk::base64 { /** * Check if the character is a %base64 character, A-Za-z0-9 and +/. @@ -72,9 +71,9 @@ * \param ch the character to test * \return true if valid */ -inline bool is_base64(char ch) noexcept +inline auto is_base64(char ch) noexcept -> bool { - return std::isalnum(ch) != 0 || ch == '+' || ch == '/'; + return std::isalnum(ch) != 0 || ch == '+' || ch == '/'; } /** @@ -84,9 +83,9 @@ * \param ch the character * \return true if the character is valid */ -inline bool is_valid(char ch) noexcept +inline auto is_valid(char ch) noexcept -> bool { - return is_base64(ch) || ch == '='; + return is_base64(ch) || ch == '='; } /** @@ -96,13 +95,13 @@ * \param value the value * \return the %base64 character for value */ -inline char lookup(unsigned char value) noexcept +inline auto lookup(unsigned char value) noexcept -> char { - static const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + static std::string_view table("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); - assert(value < 64); + assert(value < 64); - return table[value]; + return table[value]; } /** @@ -115,18 +114,18 @@ * auto b64 = base64::rlookup('D') // 3 * ```` */ -inline unsigned char rlookup(char ch) noexcept +inline auto rlookup(char ch) noexcept -> unsigned char { - assert(is_base64(ch)); + assert(is_base64(ch)); - if (ch >= '0' && ch <= '9') - return static_cast<unsigned char>(ch + 4); - if (ch >= 'A' && ch <= 'Z') - return static_cast<unsigned char>(ch - 65); - if (ch >= 'a' && ch <= 'z') - return static_cast<unsigned char>(ch - 71); + if (ch >= '0' && ch <= '9') + return static_cast<unsigned char>(ch + 4); + if (ch >= 'A' && ch <= 'Z') + return static_cast<unsigned char>(ch - 65); + if (ch >= 'a' && ch <= 'z') + return static_cast<unsigned char>(ch - 71); - return (ch == '+') ? 62U : 63U; + return (ch == '+') ? 62U : 63U; } /** @@ -142,22 +141,22 @@ * \return output */ template <typename InputIt, typename OutputIt> -OutputIt encode(InputIt input, InputIt end, OutputIt output) +auto encode(InputIt input, InputIt end, OutputIt output) -> OutputIt { - while (input != end) { - char inputbuf[3] = { 0, 0, 0 }; - int count; + while (input != end) { + char inputbuf[3] = { 0, 0, 0 }; + int count; - for (count = 0; count < 3 && input != end; ++count) - inputbuf[count] = *input++; + for (count = 0; count < 3 && input != end; ++count) + inputbuf[count] = *input++; - *output++ = lookup(inputbuf[0] >> 2 & 0x3f); - *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f)); - *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03)); - *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f); - } + *output++ = lookup(inputbuf[0] >> 2 & 0x3f); + *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f)); + *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03)); + *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f); + } - return output; + return output; } /** @@ -174,39 +173,39 @@ * \throw std::invalid_argument on bad %base64 string */ template <typename InputIt, typename OutputIt> -OutputIt decode(InputIt input, InputIt end, OutputIt output) +auto decode(InputIt input, InputIt end, OutputIt output) -> OutputIt { - while (input != end) { - char inputbuf[4] = { -1, -1, -1, -1 }; - int count; + while (input != end) { + char inputbuf[4] = { -1, -1, -1, -1 }; + int count; - for (count = 0; count < 4 && input != end; ++count) { - // Check if the character is valid and get its value. - if ((*input == '=' && count <= 1) || !is_valid(*input)) - throw std::invalid_argument("invalid base64 string"); - if (is_base64(*input)) - inputbuf[count] = static_cast<char>(rlookup(*input)); + for (count = 0; count < 4 && input != end; ++count) { + // Check if the character is valid and get its value. + if ((*input == '=' && count <= 1) || !is_valid(*input)) + throw std::invalid_argument("invalid base64 string"); + if (is_base64(*input)) + inputbuf[count] = static_cast<char>(rlookup(*input)); - input++; - } + input++; + } - if (count != 4) - throw std::invalid_argument("truncated string"); + if (count != 4) + throw std::invalid_argument("truncated string"); - *output++ = static_cast<char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03)); + *output++ = static_cast<char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03)); - if (inputbuf[2] != -1) - *output++ = static_cast<char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f)); - if (inputbuf[3] != -1) { - // "XY=Z" is not allowed. - if (inputbuf[2] == -1) - throw std::invalid_argument("invalid base64 string"); + if (inputbuf[2] != -1) + *output++ = static_cast<char>(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f)); + if (inputbuf[3] != -1) { + // "XY=Z" is not allowed. + if (inputbuf[2] == -1) + throw std::invalid_argument("invalid base64 string"); - *output++ = static_cast<char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f)); - } - } + *output++ = static_cast<char>(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f)); + } + } - return output; + return output; } /** @@ -215,13 +214,13 @@ * \param input the input string * \return the %base64 formatted string */ -inline std::string encode(const std::string &input) +inline auto encode(std::string_view input) -> std::string { - std::string result; + std::string result; - encode(input.begin(), input.end(), std::back_inserter(result)); + encode(input.begin(), input.end(), std::back_inserter(result)); - return result; + return result; } /** @@ -231,17 +230,15 @@ * \return the original string * \throw std::invalid_argument on bad %base64 string */ -inline std::string decode(const std::string &input) +inline auto decode(std::string_view input) -> std::string { - std::string result; + std::string result; - decode(input.begin(), input.end(), std::back_inserter(result)); + decode(input.begin(), input.end(), std::back_inserter(result)); - return result; + return result; } -} // !base64 +} // !mlk::base64 -} // !mlk - -#endif // !MALIKANIA_BASE64_HPP +#endif // !MALIKANIA_MAP_BASE64_HPP
--- a/tools/map/main.cpp Fri Oct 19 20:30:16 2018 +0200 +++ b/tools/map/main.cpp Sat Oct 20 20:38:43 2018 +0200 @@ -31,6 +31,7 @@ #include <vector> #include <stdexcept> #include <string> +#include <utility> #include <boost/algorithm/string/trim.hpp> #include <boost/endian/conversion.hpp> @@ -51,62 +52,29 @@ namespace io = boost::iostreams; namespace ptree = boost::property_tree; +using boost::format; +using boost::str; + +using nlohmann::json; + +/* + * Byte array for layers. + */ +using layer = std::vector<std::uint32_t>; + namespace mlk { namespace { /* - * data - * ------------------------------------------------------------------ - * - * Store map data. - */ -class data { -public: - std::string title; - std::string tileset; - std::uint64_t width{0}; - std::uint64_t height{0}; - std::uint8_t cell_width{0}; - std::uint8_t cell_height{0}; - std::vector<std::uint32_t> background; - std::vector<std::uint32_t> foreground; -}; - -/* - * load_properties - * ------------------------------------------------------------------ - * - * Extract the following properties from the map: - * - * <map> - * <properties> - * <property name="title" value="..." /> - * </properties> - * </map> - */ -void load_properties(data& result, const ptree::ptree& tree) -{ - for (const auto& p : tree) { - if (p.first != "property") - continue; - - auto name = p.second.get<std::string>("<xmlattr>.name"); - - if (name == "title") - result.title = p.second.get<std::string>("<xmlattr>.value"); - } -} - -/* * load_layer_data_xml * ------------------------------------------------------------------ * * Convert the <data> node when saved as XML. */ -std::vector<std::uint32_t> load_layer_data_xml(const ptree::ptree& tree) +auto load_layer_data_xml(const ptree::ptree& tree) -> layer { - std::vector<std::uint32_t> data; + layer data; for (const auto& t : tree) data.push_back(t.second.get<int>("<xmlattr>.gid")); @@ -120,10 +88,10 @@ * * Convert the <data> node when saved as CSV. */ -std::vector<std::uint32_t> load_layer_data_csv(const std::string& csv) +auto load_layer_data_csv(const std::string& csv) -> layer { std::istringstream iss(csv); - std::vector<std::uint32_t> data; + layer data; for (std::string line; std::getline(iss, line, ','); ) data.push_back(std::stoi(line)); @@ -139,10 +107,10 @@ * * The data is an array of 32 bits unsigned int in the little endian format. */ -std::vector<std::uint32_t> load_layer_data_array(std::string bytes) +auto load_layer_data_array(std::string bytes) -> layer { std::uint32_t* array = reinterpret_cast<std::uint32_t*>(&bytes[0]); - std::vector<std::uint32_t> data; + layer data; for (auto ptr = array; ptr != &array[bytes.length() / 4]; ++ptr) data.push_back(*ptr); @@ -154,19 +122,20 @@ * load_layer_data_array_compressed * ------------------------------------------------------------------ * + * Convert the <data> node when saved as compressed base64 data. + * * Decompress the data before parsing, supports zlib and gzip. */ -std::vector<std::uint32_t> load_layer_data_array_compressed(const std::string& bytes, - const std::string& compression) +auto load_layer_data_array_compressed(const std::string& bytes, + const std::string& compression) -> layer { io::filtering_streambuf<io::input> in; std::string out; - if (compression == "zlib") { + if (compression == "zlib") in.push(io::zlib_decompressor()); - } else { + else in.push(io::gzip_decompressor()); - } in.push(boost::make_iterator_range(bytes)); io::copy(in, io::back_inserter(out)); @@ -180,31 +149,30 @@ * * Extract the <data> node from a <layer> */ -std::vector<std::uint32_t> load_layer_data(const ptree::ptree& tree) +auto load_layer_data(const ptree::ptree& tree) -> layer { auto encoding = tree.get_optional<std::string>("<xmlattr>.encoding"); auto compression = tree.get_optional<std::string>("<xmlattr>.compression"); - std::vector<std::uint32_t> data; - if (!encoding) { + layer data; + + if (!encoding) data = load_layer_data_xml(tree); - } else if (*encoding == "csv") { + else if (*encoding == "csv") data = load_layer_data_csv(boost::trim_copy(tree.get_value<std::string>())); - } else if (*encoding == "base64") { + else if (*encoding == "base64") { auto b64 = base64::decode(boost::trim_copy(tree.get_value<std::string>())); - if (!compression) { + if (!compression) data = load_layer_data_array(std::move(b64)); - } else if (*compression == "zlib" || *compression == "gzip") { + else if (*compression == "zlib" || *compression == "gzip") data = load_layer_data_array_compressed(b64, *compression); - } // Convert endianness if system is not little endian. - if (boost::endian::order::native != boost::endian::order::little) { + if (boost::endian::order::native != boost::endian::order::little) std::transform(data.begin(), data.end(), data.begin(), [] (auto c) { return boost::endian::endian_reverse(c); }); - } } return data; @@ -222,24 +190,31 @@ * </layer> * </map> */ -void load_layer(data& map, const ptree::ptree& tree) +auto load_layers(const ptree::ptree& tree) -> std::pair<layer, layer> { - auto name = tree.get<std::string>("<xmlattr>.name"); + layer background, foreground; + + for (const auto& pair : tree.get_child("map")) { + if (pair.first != "layer") + continue; + + const auto name = pair.second.get<std::string>("<xmlattr>.name"); - if (name != "background" && name != "foreground") { - throw std::runtime_error(boost::str(boost::format( - "invalid layer name given '%s'\n" - "(only 'foreground' and 'background' is supported)" - ) % name)); + if (name != "background" && name != "foreground") + throw std::runtime_error(str(format( + "abort: invalid layer name given '%s'\n" + "(only 'foreground' and 'background' is supported)" + ) % name)); + + const auto data = load_layer_data(pair.second.get_child("data")); + + if (name == "background") + background = std::move(data); + else + foreground = std::move(data); } - auto data = load_layer_data(tree.get_child("data")); - - if (name == "background") { - map.background = std::move(data); - } else { - map.foreground = std::move(data); - } + return { background, foreground }; } /* @@ -248,27 +223,12 @@ * * Load the .tmx file from the path and return it. */ -data load(const std::string& path) +auto load_tmx(const fs::path& path) -> ptree::ptree { - data result; ptree::ptree tree; - ptree::read_xml(path, tree); + ptree::read_xml(path.string(), tree); - result.width = tree.get<int>("map.<xmlattr>.width"); - result.height = tree.get<int>("map.<xmlattr>.height"); - result.cell_height = tree.get<int>("map.<xmlattr>.tilewidth"); - result.cell_width = tree.get<int>("map.<xmlattr>.tileheight"); - result.tileset = tree.get<std::string>("map.tileset.image.<xmlattr>.source"); - - for (const auto& pair : tree.get_child("map")) { - if (pair.first == "properties") { - load_properties(result, pair.second); - } else if (pair.first == "layer") { - load_layer(result, pair.second); - } - } - - return result; + return tree; } /* @@ -277,20 +237,35 @@ * * Save map metadata into the specified file. */ -void save_meta(const data& map, const fs::path& path) +void save_meta(const ptree::ptree& xml, const fs::path& path) { - auto json = nlohmann::json::object({ - { "title", map.title }, - { "tileset", map.tileset }, - { "size", { map.width, map.height } }, - { "cell", { map.cell_width, map.cell_height } }, + auto json = json::object({ + { "tileset", xml.get<std::string>("map.tileset.image.<xmlattr>.source") }, + { "size", { + xml.get<int>("map.<xmlattr>.width"), + xml.get<int>("map.<xmlattr>.height") + }}, + { "cell", { + xml.get<int>("map.<xmlattr>.tilewidth"), + xml.get<int>("map.<xmlattr>.tileheight") + }} + }); + for (const auto& pair : xml.get_child("map.properties")) { + if (pair.first != "property") + continue; + + const auto name = pair.second.get<std::string>("<xmlattr>.name"); + + if (name == "title") + json["title"] = pair.second.get<std::string>("<xmlattr>.value"); + } + std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc); - if (!out) { + if (!out) throw std::runtime_error(std::strerror(errno)); - } out << json.dump(4) << std::endl; } @@ -301,21 +276,22 @@ * * Save the map tileset info the specified path. */ -void save_map(const data& map, const fs::path& path) +void save_map(const ptree::ptree& xml, const fs::path& path) { std::ofstream out(path.string(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); - if (!out) { + if (!out) throw std::runtime_error(std::strerror(errno)); - } + + auto [ background, foreground ] = load_layers(xml); out.write( - reinterpret_cast<const char*>(map.background.data()), - sizeof (map.background[0]) * map.background.size() + reinterpret_cast<const char*>(background.data()), + sizeof (background[0]) * background.size() ); out.write( - reinterpret_cast<const char*>(map.foreground.data()), - sizeof (map.foreground[0]) * map.foreground.size() + reinterpret_cast<const char*>(foreground.data()), + sizeof (foreground[0]) * foreground.size() ); } @@ -324,13 +300,13 @@ * ------------------------------------------------------------------ * * Save the map data into the JSON file and map data. - * - * Check MAP.md for the specifications of the JSON file. */ -void save(const data& map, const fs::path& meta, const fs::path& pack) +void save(const fs::path& input, const fs::path& meta, const fs::path& pack) { - save_meta(map, meta); - save_map(map, pack); + const auto xml = load_tmx(input); + + save_meta(xml, meta); + save_map(xml, pack); } } // !namespace @@ -361,7 +337,7 @@ map = fs::path(argv[0]).replace_extension(".map"); } - mlk::save(mlk::load(argv[0]), meta, map); + mlk::save(argv[0], meta, map); } catch (const std::exception& ex) { std::cerr << ex.what() << std::endl; return 1;