Browse Source

Ignore curly brackets inside string literals in data expressions (#190)

* Ignore curly brackets inside string literals in data expressions

* fix line endings, thanks windows git

* use string logic inside dataviewtext initialize

* fix line endings
Maximilian Stark 4 years ago
parent
commit
3383c01e4d

+ 2 - 1
Source/Core/BaseXMLParser.cpp

@@ -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])
@@ -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);

+ 39 - 17
Source/Core/DataViewDefault.cpp

@@ -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"
@@ -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;
 }
 

+ 2 - 1
Source/Core/Factory.cpp

@@ -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);

+ 17 - 11
Source/Core/XMLParseTools.cpp

@@ -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
 	{

+ 2 - 1
Source/Core/XMLParseTools.h

@@ -64,8 +64,9 @@ public:
     /// 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

+ 56 - 0
Tests/Source/UnitTests/DataBinding.cpp

@@ -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
 {
@@ -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();
+}