From dc8bb0f854f521874762e7930358c8827d058251 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Tue, 29 Aug 2023 22:59:10 +0100 Subject: [PATCH] Support old message placeholder syntax This allows the next release of libloot to be used with existing masterlists and userlists, and they can then be converted to the new syntax before a future libloot release drops support for the old syntax. While it's based on Boost.Format's syntax, which is more complex than supported by this commit, the masterlist syntax docs only mentioned and gave examples of what is supported by this commit, and the masterlists only contain placeholders %1%, %2% and %3%. --- src/api/metadata/yaml/message.h | 32 +++++++++++++++++-- .../api/internals/metadata/message_test.h | 22 +++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/api/metadata/yaml/message.h b/src/api/metadata/yaml/message.h index effb1f3e..8991082f 100644 --- a/src/api/metadata/yaml/message.h +++ b/src/api/metadata/yaml/message.h @@ -110,10 +110,38 @@ struct convert { formatArgStore.push_back(sub); } + static const std::regex boostSyntax( + "%(\\d+)%", std::regex::ECMAScript | std::regex::icase); + for (auto& mc : content) { + // Replace the old Boost.Format placeholder syntax for backward + // compatibility. To be removed after at least one major release of + // libloot to allow migration to the new syntax. + auto text = mc.GetText(); + std::smatch match; + while ( + std::regex_search(text.cbegin(), text.cend(), match, boostSyntax)) { + if (match.size() > 1) { + const auto n = std::stoul(match[1]); + if (n > 0) { + const auto newPlaceholder = "{" + std::to_string(n - 1) + "}"; + text.replace(match[0].first, match[0].second, newPlaceholder); + } else { + throw RepresentationException( + node.Mark(), + "bad conversion: found zero-indexed placeholder using old " + "syntax"); + } + } else { + throw RepresentationException(node.Mark(), + "bad conversion: only partially " + "matched old placeholder syntax"); + } + } + try { - const auto text = fmt::vformat(mc.GetText(), formatArgStore); - mc = loot::MessageContent(text, mc.GetLanguage()); + const auto formattedText = fmt::vformat(text, formatArgStore); + mc = loot::MessageContent(formattedText, mc.GetLanguage()); } catch (const fmt::format_error& e) { throw RepresentationException( node.Mark(), diff --git a/src/tests/api/internals/metadata/message_test.h b/src/tests/api/internals/metadata/message_test.h index 0a6e4d8d..b531264e 100644 --- a/src/tests/api/internals/metadata/message_test.h +++ b/src/tests/api/internals/metadata/message_test.h @@ -669,6 +669,28 @@ TEST_P(MessageTest, message.GetContent()); } +TEST_P(MessageTest, decodingFromYamlShouldAcceptPercentagePlaceholderSyntax) { + YAML::Node node = YAML::Load( + "type: say\n" + "content: content %1% %2% %3% %4% %5% %6% %7% %8% %9% %10% %11%\n" + "subs:\n" + " - a\n" + " - b\n" + " - c\n" + " - d\n" + " - e\n" + " - f\n" + " - g\n" + " - h\n" + " - i\n" + " - j\n" + " - k"); + Message message = node.as(); + + ASSERT_EQ(1, message.GetContent().size()); + EXPECT_EQ("content a b c d e f g h i j k", message.GetContent()[0].GetText()); +} + TEST_P(MessageTest, decodingFromYamlShouldThrowIfAnInvalidConditionIsGiven) { YAML::Node node = YAML::Load( "type: say\n"