diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1de8c..87aa111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ template: - fixed `for_each()` compilation error on GCC <= 7 (#197) (@sagi-ottopia, @damirbarr) - fixed `FLT_RADIX` check getting broken by Intel MKL headers (#202) (@iago-lito) - fixed keys containing `\t` incorrectly formatting as bare keys (@jasmine-zhu, @arp242) +- fixed keys containing `\t` and `\n` not round-tripping correctly (@arp242) #### Additions diff --git a/include/toml++/impl/formatter.hpp b/include/toml++/impl/formatter.hpp index a4174d3..0c97833 100644 --- a/include/toml++/impl/formatter.hpp +++ b/include/toml++/impl/formatter.hpp @@ -149,7 +149,10 @@ TOML_IMPL_NAMESPACE_START void print_unformatted(std::string_view); TOML_EXPORTED_MEMBER_FUNCTION - void print_string(std::string_view str, bool allow_multi_line = true, bool allow_bare = false); + void print_string(std::string_view str, + bool allow_multi_line = true, + bool allow_bare = false, + bool allow_literal_whitespace = true); TOML_EXPORTED_MEMBER_FUNCTION void print(const value&); diff --git a/include/toml++/impl/formatter.inl b/include/toml++/impl/formatter.inl index 5be3836..f429d32 100644 --- a/include/toml++/impl/formatter.inl +++ b/include/toml++/impl/formatter.inl @@ -113,7 +113,10 @@ TOML_IMPL_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void formatter::print_string(std::string_view str, bool allow_multi_line, bool allow_bare) + void formatter::print_string(std::string_view str, + bool allow_multi_line, + bool allow_bare, + bool allow_literal_whitespace) { if (str.empty()) { @@ -206,8 +209,10 @@ TOML_IMPL_NAMESPACE_START bad_unicode(); } - // strings with line breaks and tabs can't be bare - if (!!(traits & (formatted_string_traits::line_breaks | formatted_string_traits::tabs))) + // strings with line breaks, tabs, and single-quotes can't be bare + if (!!(traits + & (formatted_string_traits::line_breaks | formatted_string_traits::tabs + | formatted_string_traits::single_quotes))) traits |= formatted_string_traits::non_bare; // if the string meets the requirements of being 'bare' we can emit a bare string @@ -218,17 +223,20 @@ TOML_IMPL_NAMESPACE_START print_unformatted(str); return; } + const auto real_tabs_allowed = allow_literal_whitespace && real_tabs_in_strings_allowed(); // determine if this should be a multi-line string (triple-quotes) - const auto multi_line = allow_multi_line // + const auto multi_line = allow_literal_whitespace // + && allow_multi_line // && multi_line_strings_allowed() // && !!(traits & formatted_string_traits::line_breaks); // determine if this should be a literal string (single-quotes with no escaping) - const auto literal = literal_strings_allowed() // - && !(traits & formatted_string_traits::control_chars) // - && (!(traits & formatted_string_traits::single_quotes) || multi_line) // - && (!(traits & formatted_string_traits::tabs) || real_tabs_in_strings_allowed()) // + const auto literal = literal_strings_allowed() // + && !(traits & formatted_string_traits::control_chars) // + && (!(traits & formatted_string_traits::single_quotes) || multi_line) // + && (!(traits & formatted_string_traits::tabs) || real_tabs_allowed) // + && (!(traits & formatted_string_traits::line_breaks) || multi_line) // && (!(traits & formatted_string_traits::non_ascii) || unicode_allowed); // literal strings (single quotes, no escape codes) @@ -244,8 +252,6 @@ TOML_IMPL_NAMESPACE_START // anything from here down is a non-literal string, so requires iteration and escaping. print_unformatted(multi_line ? R"(""")"sv : R"(")"sv); - const auto real_tabs_allowed = real_tabs_in_strings_allowed(); - // ascii fast path if (!(traits & formatted_string_traits::non_ascii)) { diff --git a/include/toml++/impl/toml_formatter.inl b/include/toml++/impl/toml_formatter.inl index b85d5ea..e764448 100644 --- a/include/toml++/impl/toml_formatter.inl +++ b/include/toml++/impl/toml_formatter.inl @@ -130,7 +130,7 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE void toml_formatter::print(const key& k) { - print_string(k.str(), false, true); + print_string(k.str(), false, true, false); } TOML_EXTERNAL_LINKAGE diff --git a/tests/user_feedback.cpp b/tests/user_feedback.cpp index 45141ff..07b4949 100644 --- a/tests/user_feedback.cpp +++ b/tests/user_feedback.cpp @@ -415,19 +415,33 @@ b = [] SECTION("tomlplusplus/issues/176") // https://github.com/marzer/tomlplusplus/issues/176 { - parsing_should_succeed(FILE_LINE_ARGS, - R"( + parsing_should_succeed(FILE_LINE_ARGS, " a = \"x\\ty\""sv); + parsing_should_succeed(FILE_LINE_ARGS, "\"a\" = \"x\\ty\""sv); + parsing_should_succeed(FILE_LINE_ARGS, "\"a\tb\" = \"x\\ty\""sv); + parsing_should_fail(FILE_LINE_ARGS, "\"a\nb\" = \"x\\ty\""sv); // literal newline in single-line key + + static constexpr auto input = R"( "a" = "x\ty" "a\tb" = "x\ty" - )", - [](auto&& tbl) + "a\nb" = "x\ty" + )"sv; + + static constexpr auto output = "a = 'x\ty'\n" + "\"a\\tb\" = 'x\ty'\n" // tab and newlines in keys should be emitted + "\"a\\nb\" = 'x\ty'" // as escapes, not literals + ""sv; + + parsing_should_succeed(FILE_LINE_ARGS, + input, + [&](auto&& tbl) { CHECK(tbl["a"]); CHECK(tbl["a\tb"]); + CHECK(tbl["a\nb"]); std::stringstream ss; ss << tbl; - CHECK(ss.str() == "a = 'x\ty'\n'a\tb' = 'x\ty'"sv); + CHECK(ss.str() == output); }); } } diff --git a/toml.hpp b/toml.hpp index acfbc33..5fd7886 100644 --- a/toml.hpp +++ b/toml.hpp @@ -9782,7 +9782,10 @@ TOML_IMPL_NAMESPACE_START void print_unformatted(std::string_view); TOML_EXPORTED_MEMBER_FUNCTION - void print_string(std::string_view str, bool allow_multi_line = true, bool allow_bare = false); + void print_string(std::string_view str, + bool allow_multi_line = true, + bool allow_bare = false, + bool allow_literal_whitespace = true); TOML_EXPORTED_MEMBER_FUNCTION void print(const value&); @@ -16468,7 +16471,10 @@ TOML_IMPL_NAMESPACE_START } TOML_EXTERNAL_LINKAGE - void formatter::print_string(std::string_view str, bool allow_multi_line, bool allow_bare) + void formatter::print_string(std::string_view str, + bool allow_multi_line, + bool allow_bare, + bool allow_literal_whitespace) { if (str.empty()) { @@ -16561,8 +16567,10 @@ TOML_IMPL_NAMESPACE_START bad_unicode(); } - // strings with line breaks and tabs can't be bare - if (!!(traits & (formatted_string_traits::line_breaks | formatted_string_traits::tabs))) + // strings with line breaks, tabs, and single-quotes can't be bare + if (!!(traits + & (formatted_string_traits::line_breaks | formatted_string_traits::tabs + | formatted_string_traits::single_quotes))) traits |= formatted_string_traits::non_bare; // if the string meets the requirements of being 'bare' we can emit a bare string @@ -16573,17 +16581,20 @@ TOML_IMPL_NAMESPACE_START print_unformatted(str); return; } + const auto real_tabs_allowed = allow_literal_whitespace && real_tabs_in_strings_allowed(); // determine if this should be a multi-line string (triple-quotes) - const auto multi_line = allow_multi_line // + const auto multi_line = allow_literal_whitespace // + && allow_multi_line // && multi_line_strings_allowed() // && !!(traits & formatted_string_traits::line_breaks); // determine if this should be a literal string (single-quotes with no escaping) - const auto literal = literal_strings_allowed() // - && !(traits & formatted_string_traits::control_chars) // - && (!(traits & formatted_string_traits::single_quotes) || multi_line) // - && (!(traits & formatted_string_traits::tabs) || real_tabs_in_strings_allowed()) // + const auto literal = literal_strings_allowed() // + && !(traits & formatted_string_traits::control_chars) // + && (!(traits & formatted_string_traits::single_quotes) || multi_line) // + && (!(traits & formatted_string_traits::tabs) || real_tabs_allowed) // + && (!(traits & formatted_string_traits::line_breaks) || multi_line) // && (!(traits & formatted_string_traits::non_ascii) || unicode_allowed); // literal strings (single quotes, no escape codes) @@ -16599,8 +16610,6 @@ TOML_IMPL_NAMESPACE_START // anything from here down is a non-literal string, so requires iteration and escaping. print_unformatted(multi_line ? R"(""")"sv : R"(")"sv); - const auto real_tabs_allowed = real_tabs_in_strings_allowed(); - // ascii fast path if (!(traits & formatted_string_traits::non_ascii)) { @@ -17005,7 +17014,7 @@ TOML_NAMESPACE_START TOML_EXTERNAL_LINKAGE void toml_formatter::print(const key& k) { - print_string(k.str(), false, true); + print_string(k.str(), false, true, false); } TOML_EXTERNAL_LINKAGE