Browse Source

XML Parser: Ignore <tags> in {{ double brackets }}

Michael Ragazzon 5 years ago
parent
commit
2900df17d9

+ 3 - 2
Include/RmlUi/Core/BaseXMLParser.h

@@ -37,6 +37,7 @@ namespace Rml {
 namespace Core {
 
 class Stream;
+using XMLAttributes = Dictionary;
 
 /**
 	@author Peter Curry
@@ -92,11 +93,11 @@ class RMLUICORE_API BaseXMLParser
 		bool FindWord(String& word, const char* terminators = nullptr);
 		// Reads from the stream until the given character set is found. All
 		// intervening characters will be returned in data.
-		bool FindString(const unsigned char* string, String& data);
+		bool FindString(const char* string, String& data, bool escape_brackets = false);
 		// Returns true if the next sequence of characters in the stream
 		// matches the given string. If consume is set and this returns true,
 		// the characters will be consumed.
-		bool PeekString(const unsigned char* string, bool consume = true);
+		bool PeekString(const char* string, bool consume = true);
 
 		// Fill the buffer as much as possible, without removing any content that is still pending
 		bool FillBuffer();

+ 1 - 1
Include/RmlUi/Core/DataView.h

@@ -70,7 +70,7 @@ private:
 
 class DataViewText final : public DataView {
 public:
-	DataViewText(DataModel& model, ElementText* in_element, const String& in_text, size_t index_begin_search = 0);
+	DataViewText(DataModel& model, ElementText* in_element, const String& in_text);
 	~DataViewText();
 
 	bool Update(DataModel& model) override;

+ 1 - 2
Include/RmlUi/Core/XMLParser.h

@@ -92,7 +92,6 @@ public:
 	void PushDefaultHandler();
 
 	/// Access the current parse frame.
-	/// @return The parser's current parse frame.
 	const ParseFrame* GetParseFrame() const;
 
 protected:
@@ -105,7 +104,7 @@ protected:
 
 private:
 	// The header of the document being parsed.
-	DocumentHeader* header;
+	UniquePtr<DocumentHeader> header;
 
 	// The active node handler.
 	XMLNodeHandler* active_handler;

+ 4 - 1
Samples/basic/databinding/data/databinding.rml

@@ -90,6 +90,9 @@ p.title
 .red {
 	color: #e44;
 }
+.big {
+	font-size: 1.8em;
+}
 
 
 /***  Decorators  ***/
@@ -177,7 +180,7 @@ form h2
 		Indices: <span style="padding-left: 1em;" data-for="i : indices"> {{ i * 2 + (i > 10 ? ' wow!' | to_upper : '') }}</span>
 	</p>
 	<div data-for="invader : invaders">
-		<h1 data-class-red="rating < 20">{{invader.name}}</h1>
+		<h1 data-class-red="rating < 30" data-class-big="rating == 0">{{invader.name}}</h1>
 		<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color"/>
 		<p>
 			Numbers: <span data-for="invader.numbers"> {{it}} </span>

+ 48 - 31
Source/Core/BaseXMLParser.cpp

@@ -116,10 +116,10 @@ void BaseXMLParser::TreatElementContentAsCDATA()
 
 void BaseXMLParser::ReadHeader()
 {
-	if (PeekString((unsigned char*) "<?"))
+	if (PeekString("<?"))
 	{
 		String temp;
-		FindString((unsigned char*) ">", temp);
+		FindString(">", temp);
 	}
 }
 
@@ -133,25 +133,25 @@ void BaseXMLParser::ReadBody()
 	for(;;)
 	{
 		// Find the next open tag.
-		if (!FindString((unsigned char*) "<", data))
+		if (!FindString("<", data, true))
 			break;
 
 		// Check what kind of tag this is.
-		if (PeekString((const unsigned char*) "!--"))
+		if (PeekString("!--"))
 		{
 			// Comment.
 			String temp;
-			if (!FindString((const unsigned char*) "-->", temp))
+			if (!FindString("-->", temp))
 				break;
 		}
-		else if (PeekString((const unsigned char*) "![CDATA["))
+		else if (PeekString("![CDATA["))
 		{
 			// CDATA tag; read everything (including markup) until the ending
 			// CDATA tag.
 			if (!ReadCDATA())
 				break;
 		}
-		else if (PeekString((const unsigned char*) "/"))
+		else if (PeekString("/"))
 		{
 			if (!ReadCloseTag())
 				break;
@@ -199,14 +199,14 @@ bool BaseXMLParser::ReadOpenTag()
 
 	bool section_opened = false;
 
-	if (PeekString((const unsigned char*) ">"))
+	if (PeekString(">"))
 	{
 		// Simple open tag.
 		HandleElementStart(tag_name, XMLAttributes());
 		section_opened = true;
 	}
-	else if (PeekString((const unsigned char*) "/") &&
-			 PeekString((const unsigned char*) ">"))
+	else if (PeekString("/") &&
+			 PeekString(">"))
 	{
 		// Empty open tag.
 		HandleElementStart(tag_name, XMLAttributes());
@@ -222,13 +222,13 @@ bool BaseXMLParser::ReadOpenTag()
 		if (!ReadAttributes(attributes))
 			return false;
 
-		if (PeekString((const unsigned char*) ">"))
+		if (PeekString(">"))
 		{
 			HandleElementStart(tag_name, attributes);
 			section_opened = true;
 		}
-		else if (PeekString((const unsigned char*) "/") &&
-				 PeekString((const unsigned char*) ">"))
+		else if (PeekString("/") &&
+				 PeekString(">"))
 		{
 			HandleElementStart(tag_name, attributes);
 			HandleElementEnd(tag_name);
@@ -280,7 +280,7 @@ bool BaseXMLParser::ReadCloseTag()
 	}
 
 	String tag_name;
-	if (!FindString((const unsigned char*) ">", tag_name))
+	if (!FindString(">", tag_name))
 		return false;
 
 	HandleElementEnd(StringUtilities::StripWhitespace(tag_name));
@@ -305,16 +305,16 @@ bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes)
 		}
 		
 		// Check if theres an assigned value
-		if (PeekString((const unsigned char*)"="))
+		if (PeekString("="))
 		{
-			if (PeekString((const unsigned char*) "\""))
+			if (PeekString("\""))
 			{
-				if (!FindString((const unsigned char*) "\"", value))
+				if (!FindString("\"", value))
 					return false;
 			}
-			else if (PeekString((const unsigned char*) "'"))
+			else if (PeekString("'"))
 			{
-				if (!FindString((const unsigned char*) "'", value))
+				if (!FindString("'", value))
 					return false;
 			}
 			else if (!FindWord(value, "/>"))
@@ -326,8 +326,8 @@ bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes)
  		attributes[attribute] = value;
 
 		// Check for the end of the tag.
-		if (PeekString((const unsigned char*) "/", false) ||
-			PeekString((const unsigned char*) ">", false))
+		if (PeekString("/", false) ||
+			PeekString(">", false))
 			return true;
 	}
 }
@@ -337,7 +337,7 @@ bool BaseXMLParser::ReadCDATA(const char* tag_terminator, bool only_terminate_at
 	String cdata;
 	if (tag_terminator == nullptr)
 	{
-		FindString((const unsigned char*) "]]>", cdata);
+		FindString("]]>", cdata);
 		data += cdata;
 		return true;
 	}
@@ -345,14 +345,17 @@ bool BaseXMLParser::ReadCDATA(const char* tag_terminator, bool only_terminate_at
 	{
 		int tag_depth = 1;
 
+		// TODO: This doesn't properly handle comments and double brackets,
+		// should probably find a way to use the normal parsing flow instead.
+
 		for (;;)
 		{
 			// Search for the next tag opening.
-			if (!FindString((const unsigned char*)"<", cdata))
+			if (!FindString("<", cdata))
 				return false;
 
 			String node_raw;
-			if (!FindString((const unsigned char*)">", node_raw))
+			if (!FindString(">", node_raw))
 				return false;
 
 			String node_stripped = StringUtilities::StripWhitespace(node_raw);
@@ -428,9 +431,12 @@ bool BaseXMLParser::FindWord(String& word, const char* terminators)
 }
 
 // Reads from the stream until the given character set is found.
-bool BaseXMLParser::FindString(const unsigned char* string, String& data)
+bool BaseXMLParser::FindString(const char* string, String& data, bool escape_brackets)
 {
 	int index = 0;
+	bool in_brackets = false;
+	char previous = 0;
+
 	while (string[index])
 	{
 		if (read >= buffer + buffer_used)
@@ -439,13 +445,23 @@ bool BaseXMLParser::FindString(const unsigned char* string, String& data)
 				return false;
 		}
 
+		const char c = char(*read);
+
 		// Count line numbers
-		if (*read == '\n')
+		if (c == '\n')
 		{
 			line_number++;
 		}
 
-		if (*read == string[index])
+		if(escape_brackets)
+		{
+			if (c == '{' && previous == '{')
+				in_brackets = true;
+			else if (c == '}' && previous == '}')
+				in_brackets = false;
+		}
+
+		if (c == string[index] && !in_brackets)
 		{
 			index += 1;
 		}
@@ -453,13 +469,14 @@ bool BaseXMLParser::FindString(const unsigned char* string, String& data)
 		{
 			if (index > 0)
 			{
-				data += String((const char*)string, index);
+				data += String(string, index);
 				index = 0;
 			}
 
-			data += *read;
+			data += c;
 		}
 
+		previous = c;
 		read++;
 	}
 
@@ -468,7 +485,7 @@ bool BaseXMLParser::FindString(const unsigned char* string, String& data)
 
 // Returns true if the next sequence of characters in the stream matches the
 // given string.
-bool BaseXMLParser::PeekString(const unsigned char* string, bool consume)
+bool BaseXMLParser::PeekString(const char* string, bool consume)
 {
 	unsigned char* peek_read = read;
 
@@ -512,7 +529,7 @@ bool BaseXMLParser::PeekString(const unsigned char* string, bool consume)
 		}
 		else
 		{
-			if (*peek_read != string[i])
+			if (char(*peek_read) != string[i])
 				return false;
 
 			i++;

+ 2 - 2
Source/Core/DataView.cpp

@@ -53,7 +53,7 @@ DataView::DataView(Element* element) : attached_element(element->GetObserverPtr(
 }
 
 
-DataViewText::DataViewText(DataModel& model, ElementText* parent_element, const String& in_text, const size_t index_begin_search) : DataView(parent_element)
+DataViewText::DataViewText(DataModel& model, ElementText* parent_element, const String& in_text) : DataView(parent_element)
 {
 	text.reserve(in_text.size());
 
@@ -61,7 +61,7 @@ DataViewText::DataViewText(DataModel& model, ElementText* parent_element, const
 	bool success = true;
 
 	size_t previous_close_brackets = 0;
-	size_t begin_brackets = index_begin_search;
+	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);

+ 12 - 15
Source/Core/ElementUtilities.cpp

@@ -464,24 +464,21 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					else
 						Log::Message(Log::LT_WARNING, "Could not add data-if view to element '%s'.", element->GetAddress().c_str());
 				}
+				else if (data_type == "text")
+				{
+					// This attribute is automatically added by the Factory when double brackets '{{' are encountered in the text.
+					if (ElementText* element_text = rmlui_dynamic_cast<ElementText*>(element))
+					{
+						auto view = std::make_unique<DataViewText>(*data_model, element_text, element_text->GetText());
+						if (*view)
+							data_model->AddView(std::move(view));
+						else
+							Log::Message(Log::LT_WARNING, "Could not add data binding view to element '%s'.", element->GetAddress().c_str());
+					}
+				}
 			}
 		}
 
-		if (ElementText* element_text = rmlui_dynamic_cast<ElementText*>(element))
-		{
-			const String& text = element_text->GetText();
-
-			// Scan text for {{ data bindings }}
-			const size_t i_brackets = text.find("{{", 0);
-			if (i_brackets != String::npos)
-			{
-				auto view = std::make_unique<DataViewText>(*data_model, element_text, text, i_brackets);
-				if (*view)
-					data_model->AddView(std::move(view));
-				else
-					Log::Message(Log::LT_WARNING, "Could not add data binding view to element '%s'.", element->GetAddress().c_str());
-			}
-		}
 	}
 }
 

+ 50 - 20
Source/Core/Factory.cpp

@@ -251,14 +251,55 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 // Instances a single text element containing a string.
 bool Factory::InstanceElementText(Element* parent, const String& text)
 {
-	SystemInterface* system_interface = GetSystemInterface();
+	RMLUI_ASSERT(parent);
 
-	// Do any necessary translation. If any substitutions were made then new XML may have been introduced, so we'll
-	// have to run the data through the XML parser again.
 	String translated_data;
-	if (system_interface != nullptr &&
-		(system_interface->TranslateString(translated_data, text) > 0 ||
-		 translated_data.find("<") != String::npos))
+	if (SystemInterface* system_interface = GetSystemInterface())
+		system_interface->TranslateString(translated_data, text);
+
+	// Look for XML tags and detect double brackets for data bindings. If the text contains XML elements then run it through the XML parser again.
+	bool xml_introduced = false;
+	bool has_brackets = false;
+	bool only_white_space = true;
+	{
+		bool in_brackets = false;
+		char previous = 0;
+		for (const char c : translated_data)
+		{
+			if (!StringUtilities::IsWhitespace(c))
+				only_white_space = false;
+
+			if (c == '{' && previous == '{')
+			{
+				if (in_brackets)
+					Log::Message(Log::LT_WARNING, "Nested double brackets are illegal. %s", parent->GetAddress().c_str());
+
+				in_brackets = true;
+				has_brackets = true;
+			}
+			else if (c == '}' && previous == '}')
+			{
+				if (!in_brackets)
+					Log::Message(Log::LT_WARNING, "Closing double brackets mismatched an earlier open bracket. %s", parent->GetAddress().c_str());
+
+				in_brackets = false;
+			}
+			else if (c == '<' && !in_brackets)
+			{
+				xml_introduced = true;
+				break;
+			}
+
+			previous = c;
+		}
+	}
+
+	// If this text node only contains white-space we don't want to construct it.
+	if (only_white_space)
+		return true;
+
+
+	if (xml_introduced)
 	{
 		RMLUI_ZoneScopedNC("InstanceStream", 0xDC143C);
 		auto stream = std::make_unique<StreamMemory>(translated_data.size() + 32);
@@ -272,22 +313,11 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 	else
 	{
 		RMLUI_ZoneScopedNC("InstanceText", 0x8FBC8F);
-		// Check if this text node contains only white-space; if so, we don't want to construct it.
-		bool only_white_space = true;
-		for (size_t i = 0; i < translated_data.size(); ++i)
-		{
-			if (!StringUtilities::IsWhitespace(translated_data[i]))
-			{
-				only_white_space = false;
-				break;
-			}
-		}
-
-		if (only_white_space)
-			return true;
-
 		// Attempt to instance the element.
 		XMLAttributes attributes;
+		if(has_brackets)
+			attributes.emplace("data-text", Variant());
+
 		ElementPtr element = Factory::InstanceElement(parent, "#text", "#text", attributes);
 		if (!element)
 		{

+ 4 - 6
Source/Core/XMLParser.cpp

@@ -38,7 +38,7 @@
 namespace Rml {
 namespace Core {
 
-using NodeHandlers = UnorderedMap< String, SharedPtr<XMLNodeHandler> > ;
+using NodeHandlers = UnorderedMap< String, SharedPtr<XMLNodeHandler> >;
 static NodeHandlers node_handlers;
 static SharedPtr<XMLNodeHandler> default_node_handler;
 
@@ -53,13 +53,11 @@ XMLParser::XMLParser(Element* root)
 
 	active_handler = nullptr;
 
-	header = new DocumentHeader();
+	header = std::make_unique<DocumentHeader>();
 }
 
 XMLParser::~XMLParser()
-{
-	delete header;
-}
+{}
 
 // Registers a custom node handler to be used to a given tag.
 XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr<XMLNodeHandler> handler)
@@ -87,7 +85,7 @@ void XMLParser::ReleaseHandlers()
 
 DocumentHeader* XMLParser::GetDocumentHeader()
 {
-	return header;
+	return header.get();
 }
 
 const URL& XMLParser::GetSourceURL() const