diff --git a/example/xml_example.cpp b/example/xml_example.cpp index 8c39d80a..277fec1d 100644 --- a/example/xml_example.cpp +++ b/example/xml_example.cpp @@ -280,12 +280,47 @@ void province_example() { std::cout << ss1; } +struct text_t { + using escape_attr_t = + iguana::xml_attr_t>; + escape_attr_t ID; + std::string DisplayName; +}; +REFLECTION(text_t, ID, DisplayName); +void escape_example() { + { + std::string str = R"( + + &<> + 小强 + + )"; + using text_attr_t = + iguana::xml_attr_t>; + auto validator = [](const text_attr_t& text) { + auto v = text.value(); + auto attr = text.attr(); + assert(attr["description"] == R"("<'小强'>")"); + assert(v.ID.value() == R"(&<>)"); + assert(v.ID.attr()["ID'msg"] == R"({"msg'reply": "it's ok"})"); + assert(v.DisplayName == "小强"); + }; + text_attr_t text; + iguana::from_xml(text, str); + validator(text); + std::string ss; + iguana::to_xml(text, ss); + std::cout << ss << std::endl; + } +} + int main(void) { - some_type_example(); - lib_example(); - package_example(); - derived_object(); - cdata_example(); - province_example(); + // some_type_example(); + // lib_example(); + // package_example(); + // derived_object(); + // cdata_example(); + // province_example(); + escape_example(); return 0; } \ No newline at end of file diff --git a/iguana/xml_reader.hpp b/iguana/xml_reader.hpp index 1756a468..edc0e204 100644 --- a/iguana/xml_reader.hpp +++ b/iguana/xml_reader.hpp @@ -32,6 +32,8 @@ IGUANA_INLINE void parse_value(U &&value, It &&begin, It &&end) { value = T(&*begin, static_cast(std::distance(begin, end))); } else { + // TODO: When not parsing the value in the attribute, it is not necessary + // to escape'and " value.clear(); auto pre = begin; while (advance_until_character<'&'>(begin, end)) { @@ -99,9 +101,19 @@ IGUANA_INLINE void parse_attr(U &&value, It &&it, It &&end) { parse_value(key, key_begin, key_end); skip_sapces_and_newline(it, end); - match<'"'>(it, end); - auto value_begin = it; - auto value_end = skip_pass<'"'>(it, end); + auto value_begin = it + 1; + auto value_end = value_begin; + if (*it == '"') + IGUANA_LIKELY { + ++it; + value_end = skip_pass<'"'>(it, end); + } + else if (*it == '\'') { + ++it; + value_end = skip_pass<'\''>(it, end); + } + else + IGUANA_UNLIKELY { throw std::runtime_error("expected quote or apos"); } value_type v; parse_value(v, value_begin, value_end); value.emplace(std::move(key), std::move(v)); diff --git a/iguana/xml_util.hpp b/iguana/xml_util.hpp index e1807e54..fe99ee9c 100644 --- a/iguana/xml_util.hpp +++ b/iguana/xml_util.hpp @@ -84,6 +84,12 @@ inline constexpr auto has_equal = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { 0b0011110100111101001111010011110100111101001111010011110100111101); }; +inline constexpr auto has_apos = [](uint64_t chunk) IGUANA__INLINE_LAMBDA { + return has_zero( + chunk ^ + 0b0010011100100111001001110010011100100111001001110010011100100111); +}; + template IGUANA_INLINE void skip_sapces_and_newline(It &&it, It &&end) { while (it != end && (static_cast(*it) < 33)) { @@ -161,6 +167,8 @@ IGUANA_INLINE void skip_till(It &&it, It &&end) { test = has_square_bracket(chunk); else if constexpr (c == '=') test = has_equal(chunk); + else if constexpr (c == '\'') + test = has_apos(chunk); else static_assert(!c, "not support this character"); if (test != 0) { @@ -257,6 +265,7 @@ IGUANA_INLINE void parse_escape_xml(U &value, It &&it, It &&end) { if (is_match<'m', 'p', ';'>(it + 2, end)) { value.push_back('&'); it += 5; + return; } if (is_match<'p', 'o', 's', ';'>(it + 2, end)) { value.push_back('\''); diff --git a/iguana/xml_writer.hpp b/iguana/xml_writer.hpp index a703c68f..94e35f37 100644 --- a/iguana/xml_writer.hpp +++ b/iguana/xml_writer.hpp @@ -6,7 +6,15 @@ namespace iguana { -template +#ifdef XML_ATTR_USE_APOS +#define XML_ATTR_DELIMITER '\'' +#else +#define XML_ATTR_DELIMITER '\"' +#endif + +// TODO: improve by precaculate size +template IGUANA_INLINE void render_string_with_escape_xml(const Ch *it, SizeType length, Stream &ss) { auto end = it; @@ -19,10 +27,24 @@ IGUANA_INLINE void render_string_with_escape_xml(const Ch *it, SizeType length, continue; } #endif - // if (*it == '\'') - // IGUANA_UNLIKELY { ss.append("'"); } - // else if (*it == '"') - // IGUANA_UNLIKELY { ss.append("""); } + if constexpr (escape_quote_apos) { + if constexpr (XML_ATTR_DELIMITER == '\"') { + if (*it == '"') + IGUANA_UNLIKELY { + ss.append("""); + ++it; + continue; + } + } + else { + if (*it == '\'') + IGUANA_UNLIKELY { + ss.append("'"); + ++it; + continue; + } + } + } if (*it == '&') IGUANA_UNLIKELY { ss.append("&"); } else if (*it == '>') @@ -69,10 +91,12 @@ IGUANA_INLINE void render_head(Stream &ss, std::string_view str) { ss.push_back('>'); } -template , int> = 0> +template , int> = 0> IGUANA_INLINE void render_value(Stream &ss, const T &value) { if constexpr (string_container_v) { - render_string_with_escape_xml(value.data(), value.size(), ss); + render_string_with_escape_xml(value.data(), value.size(), + ss); } else if constexpr (num_v) { char temp[65]; @@ -121,9 +145,9 @@ inline void render_xml_attr(Stream &ss, const T &value, std::string_view name) { ss.push_back(' '); render_value(ss, k); ss.push_back('='); - ss.push_back('"'); - render_value(ss, v); - ss.push_back('"'); + ss.push_back(XML_ATTR_DELIMITER); + render_value(ss, v); + ss.push_back(XML_ATTR_DELIMITER); } ss.push_back('>'); } diff --git a/test/test_xml.cpp b/test/test_xml.cpp index d127cefa..bff8b06a 100644 --- a/test/test_xml.cpp +++ b/test/test_xml.cpp @@ -18,36 +18,6 @@ struct Owner_t { } }; REFLECTION(Owner_t, ID, DisplayName); -TEST_CASE("test escape") { - { - std::string str = R"( - - &<> - 小强 - - )"; - using Owner_attr_t = - iguana::xml_attr_t>; - auto validator = [](const Owner_attr_t &Owner) { - auto Ow = Owner.value(); - auto attr = Owner.attr(); - CHECK(attr["description"] == "<小强>"); - CHECK(Ow.ID == R"(&<>)"); - CHECK(Ow.DisplayName == "小强"); - }; - Owner_attr_t Owner; - iguana::from_xml(Owner, str); - validator(Owner); - - // std::string ss; - // iguana::to_xml(Owner, ss); - // std::cout << ss << std::endl; - // Owner_attr_t Owner1; - // iguana::from_xml(Owner1, ss); - // validator(Owner1); - } -} - struct Contents { std::string Key; std::string LastModified; @@ -786,6 +756,43 @@ TEST_CASE("test alias") { CHECK(m1.obj.y == 42); } +struct text_t { + using escape_attr_t = + iguana::xml_attr_t>; + escape_attr_t ID; + std::string DisplayName; +}; +REFLECTION(text_t, ID, DisplayName); +TEST_CASE("test escape") { + { + std::string str = R"( + + &<> + 小强 + + )"; + using text_attr_t = + iguana::xml_attr_t>; + auto validator = [](const text_attr_t &text) { + auto v = text.value(); + auto attr = text.attr(); + CHECK(attr["description"] == R"("<'小强'>")"); + CHECK(v.ID.value() == R"(&<>)"); + CHECK(v.ID.attr()["ID'msg"] == R"({"msg'reply": "it's ok"})"); + CHECK(v.DisplayName == "小强"); + }; + text_attr_t text; + iguana::from_xml(text, str); + validator(text); + std::string ss; + iguana::to_xml(text, ss); + + text_attr_t text1; + iguana::from_xml(text1, ss); + validator(text1); + } +} + // doctest comments // 'function' : must be 'attribute' - see issue #182 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) diff --git a/test/test_xml_nothrow.cpp b/test/test_xml_nothrow.cpp index 4723c1d3..4e0507ab 100644 --- a/test/test_xml_nothrow.cpp +++ b/test/test_xml_nothrow.cpp @@ -1,8 +1,7 @@ #define DOCTEST_CONFIG_IMPLEMENT #include "doctest.h" #undef THROW_UNKNOWN_KEY -#include "iguana/xml_reader.hpp" -#include "iguana/xml_writer.hpp" +#define XML_ATTR_USE_APOS #include #include #include @@ -10,6 +9,9 @@ #include #include +#include "iguana/xml_reader.hpp" +#include "iguana/xml_writer.hpp" + enum class enum_status { paid, unpaid, @@ -82,6 +84,44 @@ TEST_CASE("test exception") { CHECK_THROWS(iguana::from_xml(od, str)); } +struct text_t { + using escape_attr_t = + iguana::xml_attr_t>; + escape_attr_t ID; + std::string DisplayName; +}; +REFLECTION(text_t, ID, DisplayName); +TEST_CASE("test escape") { + { + std::string str = R"( + + &<> + 小强 + + )"; + using text_attr_t = + iguana::xml_attr_t>; + auto validator = [](const text_attr_t &text) { + auto v = text.value(); + auto attr = text.attr(); + CHECK(attr["description"] == R"("<'小强'>")"); + CHECK(v.ID.value() == R"(&<>)"); + CHECK(v.ID.attr()["ID'msg"] == R"({"msg'reply": "it's ok"})"); + CHECK(v.DisplayName == "小强"); + }; + text_attr_t text; + iguana::from_xml(text, str); + validator(text); + std::string ss; + iguana::to_xml(text, ss); + std::cout << ss << std::endl; + + text_attr_t text1; + iguana::from_xml(text1, ss); + validator(text1); + } +} + // doctest comments // 'function' : must be 'attribute' - see issue #182 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)