Browse Source

Handle raw inner xml contents of data-for elements more robustly.

Michael Ragazzon 5 years ago
parent
commit
bc6832ab9b

+ 30 - 15
Include/RmlUi/Core/BaseXMLParser.h

@@ -39,6 +39,8 @@ namespace Core {
 class Stream;
 using XMLAttributes = Dictionary;
 
+enum class XMLDataType { Text, CData, InnerXML };
+
 /**
 	@author Peter Curry
  */
@@ -54,6 +56,14 @@ class RMLUICORE_API BaseXMLParser
 		/// @param[in] tag The tag to register as containing generic character data.
 		void RegisterCDATATag(const String& tag);
 
+		/// When an XML attribute with the given name is encountered during parsing, then all content below the current
+		/// node is treated as data.
+		/// @note While children nodes are treated as data (text), it is assumed that the content represents valid XML.
+		///         The parsing proceeds as normal except that the Handle...() functions are not called until the
+		///         starting node is closed. Then, all its contents are submitted as Data (raw text string).
+		/// @note In particular, this behavior is useful for some data-binding views.
+		void RegisterInnerXMLAttribute(const String& attribute_name);
+
 		/// Parses the given stream as an XML file, and calls the handlers when
 		/// interesting phenomena are encountered.
 		void Parse(Stream* stream);
@@ -64,18 +74,15 @@ class RMLUICORE_API BaseXMLParser
 		/// Get the line number of the last open tag in the stream.
 		int GetLineNumberOpenTag() const;
 
-		// Treats all the inner contents of the currently parsing element as CData, including children nodes.
-		void TreatElementContentAsCDATA();
-
 		/// Called when the parser finds the beginning of an element tag.
 		virtual void HandleElementStart(const String& name, const XMLAttributes& attributes);
 		/// Called when the parser finds the end of an element tag.
 		virtual void HandleElementEnd(const String& name);
 		/// Called when the parser encounters data.
-		virtual void HandleData(const String& data);
+		virtual void HandleData(const String& data, XMLDataType type);
 
-		/// Returns the source URL of this parse. Only valid during parsing.
-		const URL& GetSourceURL() const;
+	protected:
+		const URL* GetSourceURLPtr() const;
 
 	private:
 		const URL* source_url = nullptr;
@@ -86,14 +93,17 @@ class RMLUICORE_API BaseXMLParser
 		bool AtEnd() const;
 		char Look() const;
 
+		void HandleElementStartInternal(const String& name, const XMLAttributes& attributes);
+		void HandleElementEndInternal(const String& name);
+		void HandleDataInternal(const String& data, XMLDataType type);
+
 		void ReadHeader();
 		void ReadBody();
-
-
 		bool ReadOpenTag();
-		bool ReadCloseTag();
-		bool ReadAttributes(XMLAttributes& attributes);
-		bool ReadCDATA(const char* tag_terminator = nullptr, bool only_terminate_at_same_xml_depth = false);
+
+		bool ReadCloseTag(size_t xml_index_tag);
+		bool ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content);
+		bool ReadCDATA(const char* tag_terminator = nullptr);
 
 		// Reads from the stream until a complete word is found.
 		// @param[out] word Word thats been found
@@ -107,10 +117,14 @@ class RMLUICORE_API BaseXMLParser
 		// the characters will be consumed.
 		bool PeekString(const char* string, bool consume = true);
 
-		int line_number;
-		int line_number_open_tag;
-		int open_tag_depth;
-		bool treat_content_as_cdata;
+		int line_number = 0;
+		int line_number_open_tag = 0;
+		int open_tag_depth = 0;
+
+		// Enabled when an attribute for inner xml data is encountered (see description in Register...() above).
+		bool inner_xml_data = false;
+		int inner_xml_data_terminate_depth = 0;
+		size_t inner_xml_data_index_begin = 0;
 
 		// The element attributes being read.
 		XMLAttributes attributes;
@@ -118,6 +132,7 @@ class RMLUICORE_API BaseXMLParser
 		String data;
 
 		SmallUnorderedSet< String > cdata_tags;
+		SmallUnorderedSet< String > attributes_for_inner_xml_data;
 };
 
 }

+ 5 - 0
Include/RmlUi/Core/ElementUtilities.h

@@ -136,6 +136,11 @@ public:
 	/// Creates data views and data controllers if a data model applies to the element.
 	/// Attributes such as 'data-' are used to create the views and controllers.
 	static void ApplyDataViewsControllers(Element* element);
+
+	/// Creates data views that use a raw inner xml content string to construct child elements.
+	/// Right now, this only applies to the 'data-for' view.
+	/// @return true if a data view was constructed.
+	static bool ApplyStructuralDataViews(Element* element, const String& inner_xml);
 };
 
 }

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

@@ -65,7 +65,7 @@ public:
 	/// Called for element data.
 	/// @param parser The parser executing the parse.
 	/// @param data The element data.
-	virtual bool ElementData(XMLParser* parser, const String& data) = 0;
+	virtual bool ElementData(XMLParser* parser, const String& data, XMLDataType type) = 0;
 };
 
 }

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

@@ -91,13 +91,16 @@ public:
 	/// Access the current parse frame.
 	const ParseFrame* GetParseFrame() const;
 
+	/// Returns the source URL of this parse.
+	const URL& GetSourceURL() const;
+
 protected:
 	/// Called when the parser finds the beginning of an element tag.
 	void HandleElementStart(const String& name, const XMLAttributes& attributes) override;
 	/// Called when the parser finds the end of an element tag.
 	void HandleElementEnd(const String& name) override;
 	/// Called when the parser encounters data.
-	void HandleData(const String& data) override;
+	void HandleData(const String& data, XMLDataType type) override;
 
 private:
 	// The header of the document being parsed.

+ 2 - 1
Samples/basic/bitmapfont/src/FontEngineBitmap.cpp

@@ -310,7 +310,8 @@ void FontParserBitmap::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(nam
 }
 
 // Called when the parser encounters data.
-void FontParserBitmap::HandleData(const String& RMLUI_UNUSED_PARAMETER(data))
+void FontParserBitmap::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), Rml::Core::XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
 	RMLUI_UNUSED(data);
+	RMLUI_UNUSED(type);
 }

+ 1 - 1
Samples/basic/bitmapfont/src/FontEngineBitmap.h

@@ -115,7 +115,7 @@ public:
 	/// Called when the parser finds the end of an element tag.
 	void HandleElementEnd(const String& name) override;
 	/// Called when the parser encounters data.
-	void HandleData(const String& data) override;
+	void HandleData(const String& data, Rml::Core::XMLDataType type) override;
 
 	String family;
 	FontStyle style = FontStyle::Normal;

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

@@ -185,7 +185,7 @@ form h2
 		<p>
 			Numbers: <span data-for="invader.numbers"> {{it}} </span>
 		</p>
-	</div>
+	</div> 
 </panel>
 <tab>Decorators</tab>
 <panel id="decorators">

+ 2 - 1
Source/Controls/XMLNodeHandlerDataGrid.cpp

@@ -104,8 +104,9 @@ bool XMLNodeHandlerDataGrid::ElementEnd(Core::XMLParser* RMLUI_UNUSED_PARAMETER(
 	return true;
 }
 
-bool XMLNodeHandlerDataGrid::ElementData(Core::XMLParser* parser, const Rml::Core::String& data)
+bool XMLNodeHandlerDataGrid::ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
+	RMLUI_UNUSED(type);
 	Core::Element* parent = parser->GetParseFrame()->element;
 
 	// Parse the text into the parent element.

+ 1 - 1
Source/Controls/XMLNodeHandlerDataGrid.h

@@ -52,7 +52,7 @@ public:
 	/// Called when an element is closed.
 	bool ElementEnd(Core::XMLParser* parser, const Rml::Core::String& name) override;
 	/// Called for element data.
-	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data) override;
+	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType type) override;
 };
 
 }

+ 2 - 1
Source/Controls/XMLNodeHandlerTabSet.cpp

@@ -136,8 +136,9 @@ bool XMLNodeHandlerTabSet::ElementEnd(Core::XMLParser* RMLUI_UNUSED_PARAMETER(pa
 	return true;
 }
 
-bool XMLNodeHandlerTabSet::ElementData(Core::XMLParser* parser, const Rml::Core::String& data)
+bool XMLNodeHandlerTabSet::ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {	
+	RMLUI_UNUSED(type);
 	return Core::Factory::InstanceElementText(parser->GetParseFrame()->element, data);
 }
 

+ 1 - 1
Source/Controls/XMLNodeHandlerTabSet.h

@@ -51,7 +51,7 @@ public:
 	/// Called when an element is closed
 	bool ElementEnd(Core::XMLParser* parser, const Rml::Core::String& name) override;
 	/// Called for element data
-	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data) override;
+	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType type) override;
 };
 
 }

+ 3 - 1
Source/Controls/XMLNodeHandlerTextArea.cpp

@@ -69,8 +69,10 @@ bool XMLNodeHandlerTextArea::ElementEnd(Core::XMLParser* RMLUI_UNUSED_PARAMETER(
 	return true;
 }
 
-bool XMLNodeHandlerTextArea::ElementData(Core::XMLParser* parser, const Rml::Core::String& data)
+bool XMLNodeHandlerTextArea::ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
+	RMLUI_UNUSED(type);
+
 	ElementFormControlTextArea* text_area = rmlui_dynamic_cast< ElementFormControlTextArea* >(parser->GetParseFrame()->element);
 	if (text_area != nullptr)
 	{

+ 1 - 1
Source/Controls/XMLNodeHandlerTextArea.h

@@ -51,7 +51,7 @@ public:
 	/// Called when an element is closed.
 	bool ElementEnd(Core::XMLParser* parser, const Rml::Core::String& name) override;
 	/// Called for element data.
-	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data) override;
+	bool ElementData(Core::XMLParser* parser, const Rml::Core::String& data, Core::XMLDataType type) override;
 };
 
 }

+ 98 - 78
Source/Core/BaseXMLParser.cpp

@@ -35,14 +35,10 @@ namespace Rml {
 namespace Core {
 
 BaseXMLParser::BaseXMLParser()
-{
-	open_tag_depth = 0;
-	treat_content_as_cdata = false;
-}
+{}
 
 BaseXMLParser::~BaseXMLParser()
-{
-}
+{}
 
 // Registers a tag as containing general character data.
 void BaseXMLParser::RegisterCDATATag(const String& tag)
@@ -51,6 +47,11 @@ void BaseXMLParser::RegisterCDATATag(const String& tag)
 		cdata_tags.insert(StringUtilities::ToLower(tag));
 }
 
+void BaseXMLParser::RegisterInnerXMLAttribute(const String& attribute_name)
+{
+	attributes_for_inner_xml_data.insert(attribute_name);
+}
+
 // Parses the given stream as an XML file, and calls the handlers when
 // interesting phenomenon are encountered.
 void BaseXMLParser::Parse(Stream* stream)
@@ -65,7 +66,11 @@ void BaseXMLParser::Parse(Stream* stream)
 
 	xml_index = 0;
 	line_number = 1;
-	treat_content_as_cdata = false;
+	line_number_open_tag = 1;
+
+	inner_xml_data = false;
+	inner_xml_data_terminate_depth = 0;
+	inner_xml_data_index_begin = 0;
 
 	// Read (er ... skip) the header, if one exists.
 	ReadHeader();
@@ -101,22 +106,17 @@ void BaseXMLParser::HandleElementEnd(const String& RMLUI_UNUSED_PARAMETER(name))
 }
 
 // Called when the parser encounters data.
-void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data))
+void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data), XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
 	RMLUI_UNUSED(data);
+	RMLUI_UNUSED(type);
 }
 
 /// Returns the source URL of this parse. Only valid during parsing.
 
-const URL& BaseXMLParser::GetSourceURL() const
-{
-	RMLUI_ASSERT(source_url);
-	return *source_url;
-}
-
-void BaseXMLParser::TreatElementContentAsCDATA()
+const URL* BaseXMLParser::GetSourceURLPtr() const
 {
-	treat_content_as_cdata = true;
+	return source_url;
 }
 
 void BaseXMLParser::Next() {
@@ -132,6 +132,24 @@ char BaseXMLParser::Look() const {
 	return xml_source[xml_index];
 }
 
+void BaseXMLParser::HandleElementStartInternal(const String& name, const XMLAttributes& attributes)
+{
+	if (!inner_xml_data)
+		HandleElementStart(name, attributes);
+}
+
+void BaseXMLParser::HandleElementEndInternal(const String& name)
+{
+	if (!inner_xml_data)
+		HandleElementEnd(name);
+}
+
+void BaseXMLParser::HandleDataInternal(const String& data, XMLDataType type)
+{
+	if (!inner_xml_data)
+		HandleData(data, type);
+}
+
 void BaseXMLParser::ReadHeader()
 {
 	if (PeekString("<?"))
@@ -154,6 +172,8 @@ void BaseXMLParser::ReadBody()
 		if (!FindString("<", data, true))
 			break;
 
+		const size_t xml_index_tag = xml_index - 1;
+
 		// Check what kind of tag this is.
 		if (PeekString("!--"))
 		{
@@ -171,7 +191,7 @@ void BaseXMLParser::ReadBody()
 		}
 		else if (PeekString("/"))
 		{
-			if (!ReadCloseTag())
+			if (!ReadCloseTag(xml_index_tag))
 				break;
 
 			// Bail if we've hit the end of the XML data.
@@ -199,12 +219,10 @@ bool BaseXMLParser::ReadOpenTag()
 	// Increase the open depth
 	open_tag_depth++;
 
-	treat_content_as_cdata = false;
-
 	// Opening tag; send data immediately and open the tag.
 	if (!data.empty())
 	{
-		HandleData(data);
+		HandleDataInternal(data, XMLDataType::Text);
 		data.clear();
 	}
 
@@ -217,15 +235,15 @@ bool BaseXMLParser::ReadOpenTag()
 	if (PeekString(">"))
 	{
 		// Simple open tag.
-		HandleElementStart(tag_name, XMLAttributes());
+		HandleElementStartInternal(tag_name, XMLAttributes());
 		section_opened = true;
 	}
 	else if (PeekString("/") &&
 			 PeekString(">"))
 	{
 		// Empty open tag.
-		HandleElementStart(tag_name, XMLAttributes());
-		HandleElementEnd(tag_name);
+		HandleElementStartInternal(tag_name, XMLAttributes());
+		HandleElementEndInternal(tag_name);
 
 		// Tag immediately closed, reduce count
 		open_tag_depth--;
@@ -233,20 +251,21 @@ bool BaseXMLParser::ReadOpenTag()
 	else
 	{
 		// It appears we have some attributes. Let's parse them.
+		bool parse_inner_xml_as_data = false;
 		XMLAttributes attributes;
-		if (!ReadAttributes(attributes))
+		if (!ReadAttributes(attributes, parse_inner_xml_as_data))
 			return false;
 
 		if (PeekString(">"))
 		{
-			HandleElementStart(tag_name, attributes);
+			HandleElementStartInternal(tag_name, attributes);
 			section_opened = true;
 		}
 		else if (PeekString("/") &&
 				 PeekString(">"))
 		{
-			HandleElementStart(tag_name, attributes);
-			HandleElementEnd(tag_name);
+			HandleElementStartInternal(tag_name, attributes);
+			HandleElementEndInternal(tag_name);
 
 			// Tag immediately closed, reduce count
 			open_tag_depth--;
@@ -255,25 +274,32 @@ bool BaseXMLParser::ReadOpenTag()
 		{
 			return false;
 		}
+
+		if (section_opened && parse_inner_xml_as_data && !inner_xml_data)
+		{
+			inner_xml_data = true;
+			inner_xml_data_terminate_depth = open_tag_depth;
+			inner_xml_data_index_begin = xml_index;
+		}
 	}
 
 	// Check if this tag needs to be processed as CDATA.
 	if (section_opened)
 	{
-		String lcase_tag_name = StringUtilities::ToLower(tag_name);
+		const String lcase_tag_name = StringUtilities::ToLower(tag_name);
 		bool is_cdata_tag = (cdata_tags.find(lcase_tag_name) != cdata_tags.end());
 
-		if (treat_content_as_cdata || is_cdata_tag)
+		if (is_cdata_tag)
 		{
-			if (ReadCDATA(lcase_tag_name.c_str(), !is_cdata_tag))
+			if (ReadCDATA(lcase_tag_name.c_str()))
 			{
 				open_tag_depth--;
 				if (!data.empty())
 				{
-					HandleData(data);
+					HandleDataInternal(data, XMLDataType::CData);
 					data.clear();
 				}
-				HandleElementEnd(tag_name);
+				HandleElementEndInternal(tag_name);
 
 				return true;
 			}
@@ -285,12 +311,23 @@ bool BaseXMLParser::ReadOpenTag()
 	return true;
 }
 
-bool BaseXMLParser::ReadCloseTag()
+bool BaseXMLParser::ReadCloseTag(const size_t xml_index_tag)
 {
+	if (inner_xml_data && open_tag_depth == inner_xml_data_terminate_depth)
+	{
+		// Closing the tag that initiated the inner xml data parsing. Set all its contents as Data to be
+		// submitted next, and disable the mode to resume normal parsing behavior.
+		RMLUI_ASSERT(inner_xml_data_index_begin <= xml_index_tag);
+		inner_xml_data = false;
+		data = xml_source.substr(inner_xml_data_index_begin, xml_index_tag - inner_xml_data_index_begin);
+		HandleDataInternal(data, XMLDataType::InnerXML);
+		data.clear();
+	}
+
 	// Closing tag; send data immediately and close the tag.
 	if (!data.empty())
 	{
-		HandleData(data);
+		HandleDataInternal(data, XMLDataType::Text);
 		data.clear();
 	}
 
@@ -298,15 +335,17 @@ bool BaseXMLParser::ReadCloseTag()
 	if (!FindString(">", tag_name))
 		return false;
 
-	HandleElementEnd(StringUtilities::StripWhitespace(tag_name));
+	HandleElementEndInternal(StringUtilities::StripWhitespace(tag_name));
+
 
 	// Tag closed, reduce count
 	open_tag_depth--;
 
+
 	return true;
 }
 
-bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes)
+bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes, bool& parse_raw_xml_content)
 {
 	for (;;)
 	{
@@ -338,16 +377,18 @@ bool BaseXMLParser::ReadAttributes(XMLAttributes& attributes)
 			}
 		}
 
+		if (attributes_for_inner_xml_data.count(attribute) == 1)
+			parse_raw_xml_content = true;
+
  		attributes[attribute] = value;
 
 		// Check for the end of the tag.
-		if (PeekString("/", false) ||
-			PeekString(">", false))
+		if (PeekString("/", false) || PeekString(">", false))
 			return true;
 	}
 }
 
-bool BaseXMLParser::ReadCDATA(const char* tag_terminator, bool only_terminate_at_same_xml_depth)
+bool BaseXMLParser::ReadCDATA(const char* tag_terminator)
 {
 	String cdata;
 	if (tag_terminator == nullptr)
@@ -358,55 +399,34 @@ bool BaseXMLParser::ReadCDATA(const char* tag_terminator, bool only_terminate_at
 	}
 	else
 	{
-		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("<", cdata))
 				return false;
 
-			String node_raw;
-			if (!FindString(">", node_raw))
-				return false;
-
-			String node_stripped = StringUtilities::StripWhitespace(node_raw);
-			bool close_begin = false;
-			bool close_end = false;
-
-			if (!node_stripped.empty())
-			{
-				if (node_stripped.front() == '/')
-					close_begin = true;
-				else if (node_stripped.back() == '/')
-					close_end = true;
-			}
-
-			if (!close_begin && !close_end)
-				tag_depth += 1;
-			else if (close_begin && !close_end)
-				tag_depth -= 1;
-
-			if (close_begin && !close_end && (!only_terminate_at_same_xml_depth || tag_depth == 0))
+			if (PeekString("/", false))
 			{
-				String tag_name = StringUtilities::StripWhitespace(node_stripped.substr(1));
-
-				if (StringUtilities::ToLower(tag_name) == tag_terminator)
+				String tag;
+				if (FindString(">", tag))
 				{
-					data += cdata;
-					return true;
+					size_t slash_pos = tag.find('/');
+					String tag_name = StringUtilities::StripWhitespace(slash_pos == String::npos ? tag : tag.substr(slash_pos + 1));
+					if (StringUtilities::ToLower(tag_name) == tag_terminator)
+					{
+						data += cdata;
+						return true;
+					}
+					else
+					{
+						cdata += '<' + tag + '>';
+					}
 				}
+				else
+					cdata += "<";
 			}
-
-			if (only_terminate_at_same_xml_depth && tag_depth <= 0)
-			{
-				return false;
-			}
-
-			cdata += '<' + node_raw + '>';
+			else
+				cdata += "<";
 		}
 	}
 }

+ 33 - 8
Source/Core/ElementUtilities.cpp

@@ -411,7 +411,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-attr view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-attr view to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "value")
 				{
@@ -420,13 +420,13 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-value view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-value view to element: %s", element->GetAddress().c_str());
 
 					auto controller = std::make_unique<DataControllerValue>(*data_model, element, data_expression);
 					if (controller)
 						data_model->AddController(std::move(controller));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-value controller to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-value controller to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "style")
 				{
@@ -436,7 +436,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-style view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-style view to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "class")
 				{
@@ -446,7 +446,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-class view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-class view to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "rml")
 				{
@@ -454,7 +454,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-rml view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-rml view to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "if")
 				{
@@ -462,7 +462,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 					if (*view)
 						data_model->AddView(std::move(view));
 					else
-						Log::Message(Log::LT_WARNING, "Could not add data-if view to element '%s'.", element->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Could not add data-if view to element: %s", element->GetAddress().c_str());
 				}
 				else if (data_type == "text")
 				{
@@ -473,7 +473,7 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 						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());
+							Log::Message(Log::LT_WARNING, "Could not add data binding view to element: %s", element->GetAddress().c_str());
 					}
 				}
 			}
@@ -482,5 +482,30 @@ void ElementUtilities::ApplyDataViewsControllers(Element* element)
 	}
 }
 
+bool ElementUtilities::ApplyStructuralDataViews(Element* element, const String& inner_xml)
+{
+	RMLUI_ASSERT(element);
+	bool result = false;
+
+	if (DataModel* data_model = element->GetDataModel())
+	{
+		if (const Variant* attribute = element->GetAttribute("data-for"))
+		{
+			String value_bind_name = attribute->Get<String>();
+
+			auto view = std::make_unique<DataViewFor>(*data_model, element, value_bind_name, inner_xml);
+			if (*view)
+			{
+				data_model->AddView(std::move(view));
+				result = true;
+			}
+			else
+				Log::Message(Log::LT_WARNING, "Could not add data-for view to element: %s", element->GetAddress().c_str());
+		}
+	}
+
+	return result;
+}
+
 }
 }

+ 52 - 40
Source/Core/Factory.cpp

@@ -248,6 +248,49 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 	return nullptr;
 }
 
+struct ElementTextTraits {
+	bool only_white_space = true;
+	bool has_xml = false;
+	bool has_curly_brackets = false;
+};
+static ElementTextTraits ParseElementTextTraits(Element* parent, const String& text)
+{
+	ElementTextTraits result;
+
+	bool in_brackets = false;
+	char previous = 0;
+	for (const char c : text)
+	{
+		if (!StringUtilities::IsWhitespace(c))
+			result.only_white_space = false;
+
+		if (c == '{' && previous == '{')
+		{
+			if (in_brackets)
+				Log::Message(Log::LT_WARNING, "Nested double curly brackets are illegal. %s", parent->GetAddress().c_str());
+
+			in_brackets = true;
+			result.has_curly_brackets = true;
+		}
+		else if (c == '}' && previous == '}')
+		{
+			if (!in_brackets)
+				Log::Message(Log::LT_WARNING, "Closing double curly brackets mismatched an earlier open bracket. %s", parent->GetAddress().c_str());
+
+			in_brackets = false;
+		}
+		else if (c == '<' && !in_brackets)
+		{
+			result.has_xml = true;
+			break;
+		}
+
+		previous = c;
+	}
+
+	return result;
+}
+
 // Instances a single text element containing a string.
 bool Factory::InstanceElementText(Element* parent, const String& text)
 {
@@ -257,49 +300,15 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 	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;
-		}
-	}
+	// Look for XML tags and detect double curly brackets for data bindings.
+	ElementTextTraits traits = ParseElementTextTraits(parent, text);
 
 	// If this text node only contains white-space we don't want to construct it.
-	if (only_white_space)
+	if (traits.only_white_space)
 		return true;
 
-
-	if (xml_introduced)
+	// If the text contains XML elements then run it through the XML parser again.
+	if (traits.has_xml)
 	{
 		RMLUI_ZoneScopedNC("InstanceStream", 0xDC143C);
 		auto stream = std::make_unique<StreamMemory>(translated_data.size() + 32);
@@ -314,8 +323,11 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 	{
 		RMLUI_ZoneScopedNC("InstanceText", 0x8FBC8F);
 		// Attempt to instance the element.
+
 		XMLAttributes attributes;
-		if(has_brackets)
+
+		// If we have curly brackets in the text, we tag the element so that the appropriate data view (DataViewText) is constructed.
+		if(traits.has_curly_brackets)
 			attributes.emplace("data-text", Variant());
 
 		ElementPtr element = Factory::InstanceElement(parent, "#text", "#text", attributes);

+ 2 - 1
Source/Core/XMLNodeHandlerBody.cpp

@@ -76,8 +76,9 @@ bool XMLNodeHandlerBody::ElementEnd(XMLParser* RMLUI_UNUSED_PARAMETER(parser), c
 	return true;
 }
 
-bool XMLNodeHandlerBody::ElementData(XMLParser* parser, const String& data)
+bool XMLNodeHandlerBody::ElementData(XMLParser* parser, const String& data, XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
+	RMLUI_UNUSED(type);
 	return Factory::InstanceElementText(parser->GetParseFrame()->element, data);
 }
 

+ 1 - 1
Source/Core/XMLNodeHandlerBody.h

@@ -51,7 +51,7 @@ public:
 	/// Called when an element is closed
 	bool ElementEnd(XMLParser* parser, const String& name) override;
 	/// Called for element data
-	bool ElementData(XMLParser* parser, const String& data) override;
+	bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override;
 };
 
 }

+ 5 - 21
Source/Core/XMLNodeHandlerDefault.cpp

@@ -33,6 +33,7 @@
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/XMLParser.h"
+#include "../../Include/RmlUi/Core/ElementUtilities.h"
 
 
 namespace Rml {
@@ -64,11 +65,6 @@ Element* XMLNodeHandlerDefault::ElementStart(XMLParser* parser, const String& na
 	// Move and append the element to the parent
 	Element* result = parent->AppendChild(std::move(element));
 
-	if (result->GetDataModel() && attributes.count("data-for") == 1)
-	{
-		parser->TreatElementContentAsCDATA();
-	}
-
 	return result;
 }
 
@@ -80,33 +76,21 @@ bool XMLNodeHandlerDefault::ElementEnd(XMLParser* RMLUI_UNUSED_PARAMETER(parser)
 	return true;
 }
 
-bool XMLNodeHandlerDefault::ElementData(XMLParser* parser, const String& data)
+bool XMLNodeHandlerDefault::ElementData(XMLParser* parser, const String& data, XMLDataType type)
 {
 	RMLUI_ZoneScopedC(0x006400);
 
 	// Determine the parent
 	Element* parent = parser->GetParseFrame()->element;
-	
 	RMLUI_ASSERT(parent);
 
-	// TODO: Move somewhere else. Perhaps spawn a sub-element, or set an attribute with the rml contents.
-	if (auto data_model = parent->GetDataModel())
+	if (type == XMLDataType::InnerXML)
 	{
-		if(auto attribute = parent->GetAttribute("data-for"))
-		{
-			String value_bind_name = attribute->Get<String>();
-
-			auto view = std::make_unique<DataViewFor>(*data_model, parent, value_bind_name, data);
-			if (*view)
-				data_model->AddView(std::move(view));
-			else
-				Log::Message(Log::LT_WARNING, "Could not add data-for view to element '%s'.", parent->GetAddress().c_str());
-
+		// Structural data views use the raw inner xml contents of the node, submit them now.
+		if (ElementUtilities::ApplyStructuralDataViews(parent, data))
 			return true;
-		}
 	}
 
-
 	// Parse the text into the element
 	return Factory::InstanceElementText(parent, data);
 }

+ 1 - 1
Source/Core/XMLNodeHandlerDefault.h

@@ -52,7 +52,7 @@ public:
 	/// Called when an element is closed
 	bool ElementEnd(XMLParser* parser, const String& name) override;	
 	/// Called for element data
-	bool ElementData(XMLParser* parser, const String& data) override;
+	bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override;
 };
 
 }

+ 2 - 1
Source/Core/XMLNodeHandlerHead.cpp

@@ -120,8 +120,9 @@ bool XMLNodeHandlerHead::ElementEnd(XMLParser* parser, const String& name)
 	return true;
 }
 
-bool XMLNodeHandlerHead::ElementData(XMLParser* parser, const String& data)
+bool XMLNodeHandlerHead::ElementData(XMLParser* parser, const String& data, XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {
+	RMLUI_UNUSED(type);
 	const String& tag = parser->GetParseFrame()->tag;
 
 	// Store the title

+ 1 - 1
Source/Core/XMLNodeHandlerHead.h

@@ -51,7 +51,7 @@ public:
 	/// Called when an element is closed
 	bool ElementEnd(XMLParser* parser, const String& name) override;
 	/// Called for element data
-	bool ElementData(XMLParser* parser, const String& data) override;
+	bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override;
 };
 
 }

+ 2 - 1
Source/Core/XMLNodeHandlerTemplate.cpp

@@ -66,8 +66,9 @@ bool XMLNodeHandlerTemplate::ElementEnd(XMLParser* RMLUI_UNUSED_PARAMETER(parser
 	return true;
 }
 
-bool XMLNodeHandlerTemplate::ElementData(XMLParser* parser, const String& data)
+bool XMLNodeHandlerTemplate::ElementData(XMLParser* parser, const String& data, XMLDataType RMLUI_UNUSED_PARAMETER(type))
 {	
+	RMLUI_UNUSED(type);
 	return Factory::InstanceElementText(parser->GetParseFrame()->element, data);
 }
 

+ 1 - 1
Source/Core/XMLNodeHandlerTemplate.h

@@ -51,7 +51,7 @@ public:
 	/// Called when an element is closed
 	bool ElementEnd(XMLParser* parser, const String& name) override;
 	/// Called for element data
-	bool ElementData(XMLParser* parser, const String& data) override;
+	bool ElementData(XMLParser* parser, const String& data, XMLDataType type) override;
 };
 
 }

+ 9 - 2
Source/Core/XMLParser.cpp

@@ -45,6 +45,7 @@ static SharedPtr<XMLNodeHandler> default_node_handler;
 XMLParser::XMLParser(Element* root)
 {
 	RegisterCDATATag("script");
+	RegisterInnerXMLAttribute("data-for");
 
 	// Add the first frame.
 	ParseFrame frame;
@@ -110,6 +111,12 @@ const XMLParser::ParseFrame* XMLParser::GetParseFrame() const
 	return &stack.top();
 }
 
+const URL& XMLParser::GetSourceURL() const
+{
+	RMLUI_ASSERT(GetSourceURLPtr());
+	return *GetSourceURLPtr();
+}
+
 /// Called when the parser finds the beginning of an element tag.
 void XMLParser::HandleElementStart(const String& _name, const XMLAttributes& attributes)
 {
@@ -168,11 +175,11 @@ void XMLParser::HandleElementEnd(const String& _name)
 }
 
 /// Called when the parser encounters data.
-void XMLParser::HandleData(const String& data)
+void XMLParser::HandleData(const String& data, XMLDataType type)
 {
 	RMLUI_ZoneScoped;
 	if (stack.top().node_handler)
-		stack.top().node_handler->ElementData(this, data);
+		stack.top().node_handler->ElementData(this, data, type);
 }
 
 }