diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index e3be5eb1..078402ae 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -2,9 +2,9 @@ name: Clang Format Diff on: push: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] pull_request: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] jobs: build: diff --git a/.github/workflows/linux-clang.yml b/.github/workflows/linux-clang.yml index e2e7f01c..401c8570 100644 --- a/.github/workflows/linux-clang.yml +++ b/.github/workflows/linux-clang.yml @@ -2,9 +2,9 @@ name: Ubuntu (clang) on: push: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] pull_request: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] jobs: build: diff --git a/.github/workflows/linux-gcc.yml b/.github/workflows/linux-gcc.yml index caaba1ef..1e0596f8 100644 --- a/.github/workflows/linux-gcc.yml +++ b/.github/workflows/linux-gcc.yml @@ -2,9 +2,9 @@ name: Ubuntu (gcc) on: push: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] pull_request: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] jobs: build: diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 035f72de..c334ff00 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -2,9 +2,9 @@ name: macOS Monterey 12 on: push: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] pull_request: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] jobs: build: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index a93d8f62..6a9583d8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -2,9 +2,9 @@ name: Windows Server 2022 on: push: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] pull_request: - branches: [ master, fullsupport_xml] + branches: [ master, improve_xml] jobs: build: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3822a92c..b463ae1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ set(TEST_YAML test/test_yaml.cpp test/test_yaml_bech.cpp) set(YAMLBENCH benchmark/yaml_benchmark.cpp) set(TEST_NOTHROW test/test_yaml_nothrow.cpp) set(TEST_UTIL test/test_util.cpp) +set(TEST_XMLNOTHROW test/test_xml_nothrow.cpp) add_executable(json_example ${JSON_EXAMPLE}) add_executable(xml_example ${XML_EXAMPLE}) @@ -67,6 +68,7 @@ add_executable(test_yaml ${TEST_YAML}) add_executable(yaml_benchmark ${YAMLBENCH}) add_executable(test_nothrow ${TEST_NOTHROW}) add_executable(test_util ${TEST_UTIL}) +add_executable(test_xml_nothrow ${TEST_XMLNOTHROW}) # unit test option(BUILD_UNIT_TESTS "Build unit tests" ON) @@ -91,3 +93,4 @@ add_test(NAME test_xml COMMAND test_xml) add_test(NAME test_yaml COMMAND test_yaml) add_test(NAME test_nothrow COMMAND test_nothrow) add_test(NAME test_util COMMAND test_util) +add_test(NAME test_xml_nothrow COMMAND test_xml_nothrow) diff --git a/README.md b/README.md index e80960b1..7b3cd433 100644 --- a/README.md +++ b/README.md @@ -96,14 +96,10 @@ person p = {"admin", 20}; iguana::string_stream ss; // here use std::string is also ok iguana::to_xml(ss, p); std::cout << ss.str() << std::endl; -// you can also call iguana::to_xml_pretty to get pretty string -iguana::string_stream s; -iguana::to_xml_pretty(s, p); -std::cout << s.str() << std::endl; // deserialization the structure from the string std::string xml = " buke 30"; -iguana::from_xml(p, xml.data()); +iguana::from_xml(p, xml); ``` #### Serialization of yaml @@ -212,7 +208,7 @@ std::string str = R"( )"; library_t lib; -iguana::from_xml(lib, str.data()); +iguana::from_xml(lib, str); ``` #### yaml diff --git a/benchmark/xml_bench.hpp b/benchmark/xml_bench.hpp index 16827dbe..95774951 100644 --- a/benchmark/xml_bench.hpp +++ b/benchmark/xml_bench.hpp @@ -96,13 +96,13 @@ REFLECTION(rss_t, channel); // ************ struct for bench_num.xml **************** struct goods_t { - int id; - int sales; - int inventory; - double weight; - double price; - double rating; - double discount; + std::string_view id; + std::string_view sales; + std::string_view inventory; + std::string_view weight; + std::string_view price; + std::string_view rating; + std::string_view discount; }; REFLECTION(goods_t, id, sales, inventory, weight, price, rating, discount); struct storeowner_t { diff --git a/benchmark/xml_benchmark.cpp b/benchmark/xml_benchmark.cpp index 5c378b3a..e5dd563a 100644 --- a/benchmark/xml_benchmark.cpp +++ b/benchmark/xml_benchmark.cpp @@ -1,5 +1,6 @@ #include "../rapidxml/rapidxml.hpp" #include "xml_bench.hpp" +#include class ScopedTimer { public: @@ -44,16 +45,18 @@ const int iterations = 1000; void bench_de_sample_filelists() { std::string xmlfilelist = xml_file_content("../data/rpm_filelists.xml"); + rapidxml::xml_document<> doc; { ScopedTimer timer("rapidxml fastest parse rpm_filelists.xml"); for (int i = 0; i < iterations; ++i) { - rapidxml::xml_document<> doc; doc.parse(xmlfilelist.data()); + doc.clear(); } } + + filelists_t filelist; { ScopedTimer timer("iguana_xml deserialize rpm_filelists.xml"); - filelists_t filelist; for (int i = 0; i < iterations; ++i) { iguana::from_xml(filelist, xmlfilelist.begin(), xmlfilelist.end()); filelist.package.clear(); @@ -63,16 +66,18 @@ void bench_de_sample_filelists() { void bench_de_sample_rss() { std::string xmlrss = xml_file_content("../data/sample_rss.xml"); + rapidxml::xml_document<> doc; { ScopedTimer timer("rapidxml fastest parse sample_rss.xml"); for (int i = 0; i < iterations; ++i) { - rapidxml::xml_document<> doc; doc.parse(xmlrss.data()); + doc.clear(); } } + + rss_t rss; { ScopedTimer timer("iguana_xml deserialize sample_rss.xml"); - rss_t rss; for (int i = 0; i < iterations; ++i) { iguana::from_xml(rss, xmlrss.begin(), xmlrss.end()); rss.channel.item.clear(); @@ -82,28 +87,46 @@ void bench_de_sample_rss() { void bench_num() { std::string xmlnum = xml_file_content("../data/bench_num.xml"); + + store_t s; + iguana::from_xml(s, xmlnum); + auto good = s.goods[1]; + std::cout << iguana::get_number(good.sales) << ", " + << iguana::get_number(good.weight) << "\n"; + s.goods.clear(); + { ScopedTimer timer("iguana_xml deserialize bench_num.xml"); for (int i = 0; i < iterations; ++i) { - store_t s; iguana::from_xml(s, xmlnum); + s.goods.clear(); } } - store_t store; - iguana::from_xml(store, xmlnum); - std::string ss; - ss.reserve(xmlnum.size()); + + rapidxml::xml_document<> doc; { - ScopedTimer timer("iguana_xml serialize bench_num.xml"); + ScopedTimer timer("rapidxml fastest parse bench_num.xml"); for (int i = 0; i < iterations; ++i) { - iguana::to_xml(store, ss); - ss.clear(); + doc.parse(xmlnum.data()); + doc.clear(); } } + + // store_t store; + // iguana::from_xml(store, xmlnum); + // std::string ss; + // ss.reserve(xmlnum.size()); + // { + // ScopedTimer timer("iguana_xml serialize bench_num.xml"); + // for (int i = 0; i < iterations; ++i) { + // iguana::to_xml(store, ss); + // ss.clear(); + // } + // } } int main() { bench_de_sample_filelists(); bench_de_sample_rss(); - // bench_num(); + bench_num(); } diff --git a/frozen/string.h b/frozen/string.h index ccdef751..cad92bbe 100644 --- a/frozen/string.h +++ b/frozen/string.h @@ -28,6 +28,7 @@ #include "frozen/bits/hash_string.h" #include "frozen/bits/version.h" +#include #include #ifdef FROZEN_LETITGO_HAS_STRING_VIEW diff --git a/iguana/json_writer.hpp b/iguana/json_writer.hpp index 7dd5c770..4134f4f8 100644 --- a/iguana/json_writer.hpp +++ b/iguana/json_writer.hpp @@ -174,12 +174,12 @@ template IGUANA_INLINE void render_json_value(Stream &s, T &&t) { using U = typename std::decay_t; s.push_back('['); - const size_t size = std::tuple_size_v; + constexpr size_t size = std::tuple_size_v; for_each(std::forward(t), [&s, size](auto &v, auto i) IGUANA__INLINE_LAMBDA { render_json_value(s, v); - if (i != size - 1) + if (i != size - 1) [[likely]] s.push_back(','); }); s.push_back(']'); diff --git a/iguana/xml_reader.hpp b/iguana/xml_reader.hpp index a3aabe37..0f8b1384 100644 --- a/iguana/xml_reader.hpp +++ b/iguana/xml_reader.hpp @@ -134,7 +134,6 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, skip_sapces_and_newline(it, end); auto value_begin = it; auto value_end = skip_pass_smaller(it, end); - ; if (value_begin == value_end) { match_close_tag(it, end, name); return; @@ -160,6 +159,30 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end, parse_item(value.value(), it, end, name); } +// /> or skip and until the +// loose inspection here +template +IGUANA_INLINE void skip_object_value(It &&it, It &&end, std::string_view name) { + skip_till_greater(it, end); + ++it; + if (*(it - 2) == '/') { + // .../> + return; + } + // + size_t size = name.size(); + while (it != end) { + skip_till_greater(it, end); + if (*(it - size - 2) == '<' && *(it - size - 1) == '/' && + (std::string_view{&*(it - size), size} == name)) { + ++it; + return; + } + ++it; + } + throw std::runtime_error("unclosed tag: " + std::string(name)); +} + template IGUANA_INLINE void parse_item(T &value, It &&it, It &&end, std::string_view name) { @@ -242,12 +265,16 @@ IGUANA_INLINE void parse_item(T &value, It &&it, It &&end, "type must be memberptr"); using V = std::remove_reference_t; if constexpr (!cdata_t) { - detail::parse_item(value.*member_ptr, it, end, key); + parse_item(value.*member_ptr, it, end, key); } }, member_it->second); } else [[unlikely]] { +#ifdef THROW_UNKNOWN_KEY throw std::runtime_error("Unknown key: " + std::string(key)); +#else + skip_object_value(it, end, key); +#endif } skip_sapces_and_newline(it, end); } @@ -299,4 +326,10 @@ IGUANA_INLINE void from_xml(U &value, const View &view) { from_xml(value, std::begin(view), std::end(view)); } +template IGUANA_INLINE Num get_number(std::string_view str) { + Num num; + detail::parse_value(num, str.begin(), str.end()); + return num; +} + } // namespace iguana \ No newline at end of file diff --git a/iguana/xml_util.hpp b/iguana/xml_util.hpp index eda70fec..8f73f27e 100644 --- a/iguana/xml_util.hpp +++ b/iguana/xml_util.hpp @@ -165,6 +165,35 @@ template struct string_literal { constexpr const std::string_view sv() const noexcept { return {value, size}; } }; +inline constexpr auto has_zero = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return (((chunk - 0x0101010101010101) & ~chunk) & 0x8080808080808080); +}; + +inline constexpr auto has_greater = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return has_zero( + chunk ^ + 0b0011111000111110001111100011111000111110001111100011111000111110); +}; + +inline constexpr auto has_space = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return has_zero( + chunk ^ + 0b0010000000100000001000000010000000100000001000000010000000100000); +}; + +inline constexpr auto has_smaller = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return has_zero( + chunk ^ + 0b0011110000111100001111000011110000111100001111000011110000111100); +}; + +inline constexpr auto has_square_bracket = + [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return has_zero( + chunk ^ + 0b0101110101011101010111010101110101011101010111010101110101011101); + }; + IGUANA_INLINE void skip_sapces_and_newline(auto &&it, auto &&end) { while (it != end && (*it < 33)) { ++it; @@ -227,9 +256,9 @@ IGUANA_INLINE void match_close_tag(auto &&it, auto &&end, if (it == end || (*it++) != '/') [[unlikely]] { throw std::runtime_error("unclosed tag: " + std::string(key)); } - auto size = key.size(); - if ((std::distance(it, end) <= size) || (std::string_view{&*it, size} != key)) - [[unlikely]] { + size_t size = key.size(); + if (static_cast(std::distance(it, end)) <= size || + std::string_view{&*it, size} != key) [[unlikely]] { throw std::runtime_error("unclosed tag: " + std::string(key)); } it += size; @@ -243,15 +272,6 @@ IGUANA_INLINE void match_close_tag(auto &&it, auto &&end, IGUANA_INLINE void skip_till_greater(auto &&it, auto &&end) { static_assert(std::contiguous_iterator>); - auto has_zero = [](uint64_t chunk) { - return (((chunk - 0x0101010101010101) & ~chunk) & 0x8080808080808080); - }; - auto has_greater = [&](uint64_t chunk) { - return has_zero( - chunk ^ - 0b0011111000111110001111100011111000111110001111100011111000111110); - }; - if (std::distance(it, end) >= 7) [[likely]] { const auto end_m7 = end - 7; for (; it < end_m7; it += 8) { @@ -279,19 +299,6 @@ IGUANA_INLINE void skip_till_greater(auto &&it, auto &&end) { IGUANA_INLINE void skip_till_greater_or_space(auto &&it, auto &&end) { static_assert(std::contiguous_iterator>); - auto has_zero = [](uint64_t chunk) { - return (((chunk - 0x0101010101010101) & ~chunk) & 0x8080808080808080); - }; - auto has_greater = [&](uint64_t chunk) { - return has_zero( - chunk ^ - 0b0011111000111110001111100011111000111110001111100011111000111110); - }; - auto has_space = [&](uint64_t chunk) { - return has_zero( - chunk ^ - 0b0010000000100000001000000010000000100000001000000010000000100000); - }; if (std::distance(it, end) >= 7) [[likely]] { const auto end_m7 = end - 7; for (; it < end_m7; it += 8) { @@ -320,15 +327,6 @@ IGUANA_INLINE void skip_till_greater_or_space(auto &&it, auto &&end) { IGUANA_INLINE void skip_till_smaller(auto &&it, auto &&end) { static_assert(std::contiguous_iterator>); - auto has_zero = [](uint64_t chunk) { - return (((chunk - 0x0101010101010101) & ~chunk) & 0x8080808080808080); - }; - auto has_smaller = [&](uint64_t chunk) { - return has_zero( - chunk ^ - 0b0011110000111100001111000011110000111100001111000011110000111100); - }; - if (std::distance(it, end) >= 7) [[likely]] { const auto end_m7 = end - 7; for (; it < end_m7; it += 8) { @@ -356,14 +354,6 @@ IGUANA_INLINE void skip_till_smaller(auto &&it, auto &&end) { IGUANA_INLINE void skip_till_square_bracket(auto &&it, auto &&end) { static_assert(std::contiguous_iterator>); - auto has_zero = [](uint64_t chunk) { - return (((chunk - 0x0101010101010101) & ~chunk) & 0x8080808080808080); - }; - auto has_square_bracket = [&](uint64_t chunk) { - return has_zero( - chunk ^ - 0b0101110101011101010111010101110101011101010111010101110101011101); - }; if (std::distance(it, end) >= 7) [[likely]] { const auto end_m7 = end - 7; for (; it < end_m7; it += 8) { diff --git a/test/test_xml_nothrow.cpp b/test/test_xml_nothrow.cpp new file mode 100644 index 00000000..c5941136 --- /dev/null +++ b/test/test_xml_nothrow.cpp @@ -0,0 +1,77 @@ +#define DOCTEST_CONFIG_IMPLEMENT +#include "doctest.h" +#undef THROW_UNKNOWN_KEY +#include "iguana/xml_reader.hpp" +#include "iguana/xml_writer.hpp" +#include +#include +#include +#include +#include +#include + +enum class enum_status { + paid, + unpaid, +}; +struct order_t { + enum_status status; + float total; +}; +REFLECTION(order_t, status, total); + +TEST_CASE("test unkonwn key") { + auto validator = [](order_t od) { + CHECK(od.status == enum_status::unpaid); + CHECK(od.total == 65.0f); + }; + std::string str = R"( + + 12345 + 1 + + + John + ]]> + Doe +
+ 123 Main St + Anytown + CA + 12345 +
+
+ + + 67890 + Widget A + 2 + 10.00 + + + 98765 + Widget B + 3 + 15.00 + + + ]]> + 65.00 +
+ )"; + order_t od; + iguana::from_xml(od, str); + validator(od); + + std::string ss; + iguana::to_xml(od, ss); + order_t od1; + iguana::from_xml(od1, ss); + validator(od1); +} + +// doctest comments +// 'function' : must be 'attribute' - see issue #182 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) +int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP