Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore curly brackets inside string literals in data expressions #190

Merged
merged 4 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Source/Core/BaseXMLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ bool BaseXMLParser::FindString(const char* string, String& data, bool escape_bra
{
int index = 0;
bool in_brackets = false;
bool in_string = false;
char previous = 0;

while (string[index])
Expand All @@ -493,7 +494,7 @@ bool BaseXMLParser::FindString(const char* string, String& data, bool escape_bra

if(escape_brackets)
{
const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, c, previous);
const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous);
if (error_str)
{
Log::Message(Log::LT_WARNING, "XML parse error. %s", error_str);
Expand Down
56 changes: 39 additions & 17 deletions Source/Core/DataViewDefault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "DataViewDefault.h"
#include "DataExpression.h"
#include "DataModel.h"
#include "XMLParseTools.h"
#include "../../Include/RmlUi/Core/Core.h"
#include "../../Include/RmlUi/Core/DataVariable.h"
#include "../../Include/RmlUi/Core/Element.h"
Expand Down Expand Up @@ -328,35 +329,56 @@ bool DataViewText::Initialize(DataModel& model, Element* element, const String&

DataExpressionInterface expression_interface(&model, element);

size_t previous_close_brackets = 0;
size_t begin_brackets = 0;
while ((begin_brackets = in_text.find("{{", begin_brackets)) != String::npos)
{
text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.begin() + begin_brackets);
size_t cur = 0;
char previous = 0;
bool was_in_brackets = false;
bool in_brackets = false;
bool in_string = false;

const size_t begin_name = begin_brackets + 2;
const size_t end_name = in_text.find("}}", begin_name);
for(char c : in_text) {
was_in_brackets = in_brackets;

if (end_name == String::npos)
const char* error_str = XMLParseTools::ParseDataBrackets(in_brackets, in_string, c, previous);
if (error_str)
{
Log::Message(Log::LT_WARNING, "Failed to parse data view text '%s'. %s", in_text.c_str(), error_str);
return false;
}

if (!was_in_brackets && in_brackets)
{
begin_brackets = cur;
}
else if (was_in_brackets && !in_brackets)
{
DataEntry entry;
entry.index = text.size();
entry.data_expression = MakeUnique<DataExpression>(String(in_text.begin() + begin_brackets + 1, in_text.begin() + cur - 2));

DataEntry entry;
entry.index = text.size();
entry.data_expression = MakeUnique<DataExpression>(String(in_text.begin() + begin_name, in_text.begin() + end_name));
if (entry.data_expression->Parse(expression_interface, false))
data_entries.push_back(std::move(entry));

if (entry.data_expression->Parse(expression_interface, false))
data_entries.push_back(std::move(entry));
// Reset char so that it won't appended to the output
c = 0;
}
else if (!in_brackets && previous)
{
text.push_back(previous);
}

previous_close_brackets = end_name + 2;
begin_brackets = previous_close_brackets;
cur++;
previous = c;
}

if (!in_brackets && previous)
{
text.push_back(previous);
}

if (data_entries.empty())
return false;

if (previous_close_brackets < in_text.size())
text.insert(text.end(), in_text.begin() + previous_close_brackets, in_text.end());

return true;
}

Expand Down
3 changes: 2 additions & 1 deletion Source/Core/Factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,11 @@ bool Factory::InstanceElementText(Element* parent, const String& in_text)
bool has_data_expression = false;

bool inside_brackets = false;
bool inside_string = false;
char previous = 0;
for (const char c : text)
{
const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, c, previous);
const char* error_str = XMLParseTools::ParseDataBrackets(inside_brackets, inside_string, c, previous);
if (error_str)
{
Log::Message(Log::LT_WARNING, "Failed to instance text element '%s'. %s", text.c_str(), error_str);
Expand Down
28 changes: 17 additions & 11 deletions Source/Core/XMLParseTools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,24 +153,30 @@ Element* XMLParseTools::ParseTemplate(Element* element, const String& template_n
return parse_template->ParseTemplate(element);
}

const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, char c, char previous)
const char* XMLParseTools::ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous)
{
if (inside_brackets)
{
if (c == '}' && previous == '}')
inside_brackets = false;
if (c == '\'')
inside_string = !inside_string;

else if (c == '{' && previous == '{')
return "Nested double curly brackets are illegal.";
if(!inside_string)
{
if (c == '}' && previous == '}')
inside_brackets = false;

else if (c == '{' && previous == '{')
return "Nested double curly brackets are illegal.";

else if (previous == '}' && c != '}')
return "Single closing curly bracket encountered, use double curly brackets to close an expression.";
else if (previous == '}' && c != '}')
return "Single closing curly bracket encountered, use double curly brackets to close an expression.";

else if (previous == '/' && c == '>')
return "Closing double curly brackets not found, XML end node encountered first.";
else if (previous == '/' && c == '>')
return "Closing double curly brackets not found, XML end node encountered first.";

else if (previous == '<' && c == '/')
return "Closing double curly brackets not found, XML end node encountered first.";
else if (previous == '<' && c == '/')
return "Closing double curly brackets not found, XML end node encountered first.";
}
}
else
{
Expand Down
3 changes: 2 additions & 1 deletion Source/Core/XMLParseTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ class XMLParseTools
/// Determine the presence of data expression brackets inside XML data.
/// Call this for each iteration through the data string.
/// 'inside_brackets' should be initialized to false.
/// 'inside_string' should be initialized to false.
/// Returns nullptr on success, or an error string on failure.
static const char* ParseDataBrackets(bool& inside_brackets, char c, char previous);
static const char* ParseDataBrackets(bool& inside_brackets, bool& inside_string, char c, char previous);
};

} // namespace Rml
Expand Down
56 changes: 56 additions & 0 deletions Tests/Source/UnitTests/DataBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,38 @@ static const String document_rml = R"(
</rml>
)";

static const String inside_string_rml = R"(
<rml>
<head>
<title>Test</title>
<link type="text/rcss" href="/assets/rml.rcss"/>
<link type="text/template" href="/assets/window.rml"/>
<style>
body.window
{
left: 50px;
right: 50px;
top: 30px;
bottom: 30px;
max-width: -1px;
max-height: -1px;
}
</style>
</head>

<body template="window">
<div data-model="basics">

<p>{{ i0 }}</p>
<p>{{ 'i0' }}</p>
<p>{{ 'i{}23' }}</p>
<p>before {{ 'i{{test}}23' }} test</p>
<p>a {{ 'i' }} b {{ 'j' }} c</p>

</div>
</body>
</rml>
)";

struct StringWrap
{
Expand Down Expand Up @@ -393,3 +425,27 @@ TEST_CASE("databinding")

TestsShell::ShutdownShell();
}

TEST_CASE("databinding.inside_string")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);

REQUIRE(InitializeDataBindings(context));

ElementDocument* document = context->LoadDocumentFromMemory(inside_string_rml);
REQUIRE(document);
document->Show();

context->Update();
context->Render();

TestsShell::RenderLoop();

CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "before i{{test}}23 test");
CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "a i b j c");

document->Close();

TestsShell::ShutdownShell();
}