Browse Source

Add <include> element to inject templates anywhere in the document (#168)

* Rework template inclusion using new <include> element

* mark unused parameter as such

* Fix template content injection

* Add unit tests for template and include

* author

* Fix unit test issue and add warning in Element::SetAttributes when overriding

* Move attribute overriding warning into body handle

Co-authored-by: Michael Ragazzon <[email protected]>
Maximilian Stark 4 years ago
parent
commit
659f87b0a1

+ 2 - 0
CMake/FileList.cmake

@@ -47,6 +47,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerInclude.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventDispatcher.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventDispatcher.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventInstancerDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/EventInstancerDefault.h
@@ -304,6 +305,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerDataGrid.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTabSet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerTextArea.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Elements/XMLNodeHandlerInclude.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementScroll.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementScroll.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementStyle.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementText.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementText.cpp

+ 94 - 0
Source/Core/Elements/XMLNodeHandlerInclude.cpp

@@ -0,0 +1,94 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "XMLNodeHandlerInclude.h"
+#include "../../../Include/RmlUi/Core/Core.h"
+#include "../../../Include/RmlUi/Core/Factory.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "../../../Include/RmlUi/Core/XMLParser.h"
+#include "../../../Include/RmlUi/Core/ElementUtilities.h"
+#include "../XMLParseTools.h"
+
+namespace Rml {
+
+XMLNodeHandlerInclude::XMLNodeHandlerInclude()
+{
+}
+
+XMLNodeHandlerInclude::~XMLNodeHandlerInclude()
+{
+}
+
+Element* XMLNodeHandlerInclude::ElementStart(XMLParser* parser, const String& RMLUI_UNUSED_PARAMETER(name), const XMLAttributes& attributes)
+{
+	RMLUI_UNUSED(name);
+
+	Element* element = parser->GetParseFrame()->element;
+
+	// Apply the template directly into the parent
+	String template_name = Get<String>(attributes, "template", "");
+
+	if (!template_name.empty())
+	{
+		element = XMLParseTools::ParseTemplate(element, template_name);
+	}
+
+	// Tell the parser to use the element handler for all children
+	parser->PushDefaultHandler();
+	
+	return element;
+}
+
+bool XMLNodeHandlerInclude::ElementEnd(XMLParser* RMLUI_UNUSED_PARAMETER(parser), const String& RMLUI_UNUSED_PARAMETER(name))
+{
+	RMLUI_UNUSED(parser);
+	RMLUI_UNUSED(name);
+
+	return true;
+}
+
+bool XMLNodeHandlerInclude::ElementData(XMLParser* parser, const String& data, XMLDataType type)
+{
+	RMLUI_ZoneScopedC(0x006400);
+
+	// Determine the parent
+	Element* parent = parser->GetParseFrame()->element;
+	RMLUI_ASSERT(parent);
+
+	if (type == XMLDataType::InnerXML)
+	{
+		// 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);
+}
+
+} // namespace Rml

+ 57 - 0
Source/Core/Elements/XMLNodeHandlerInclude.h

@@ -0,0 +1,57 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_ELEMENTS_XMLNODEHANDLERINCLUDE_H
+#define RMLUI_CORE_ELEMENTS_XMLNODEHANDLERINCLUDE_H
+
+#include "../../../Include/RmlUi/Core/XMLNodeHandler.h"
+
+namespace Rml {
+
+/**
+	Node handler that processes the contents of the include tag.
+
+	@author Maximilian Stark
+ */
+
+class XMLNodeHandlerInclude : public XMLNodeHandler
+{
+public:
+	XMLNodeHandlerInclude();
+	virtual ~XMLNodeHandlerInclude();
+
+	/// Called when a new element is opened.
+	Element* ElementStart(XMLParser* parser, const String& name, const XMLAttributes& attributes) override;
+	/// 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, XMLDataType type) override;
+};
+
+} // namespace Rml
+#endif

+ 2 - 0
Source/Core/Factory.cpp

@@ -84,6 +84,7 @@
 #include "Elements/XMLNodeHandlerDataGrid.h"
 #include "Elements/XMLNodeHandlerDataGrid.h"
 #include "Elements/XMLNodeHandlerTabSet.h"
 #include "Elements/XMLNodeHandlerTabSet.h"
 #include "Elements/XMLNodeHandlerTextArea.h"
 #include "Elements/XMLNodeHandlerTextArea.h"
+#include "Elements/XMLNodeHandlerInclude.h"
 
 
 #include <algorithm>
 #include <algorithm>
 
 
@@ -286,6 +287,7 @@ bool Factory::Initialise()
 	XMLParser::RegisterNodeHandler("body", MakeShared<XMLNodeHandlerBody>());
 	XMLParser::RegisterNodeHandler("body", MakeShared<XMLNodeHandlerBody>());
 	XMLParser::RegisterNodeHandler("head", MakeShared<XMLNodeHandlerHead>());
 	XMLParser::RegisterNodeHandler("head", MakeShared<XMLNodeHandlerHead>());
 	XMLParser::RegisterNodeHandler("template", MakeShared<XMLNodeHandlerTemplate>());
 	XMLParser::RegisterNodeHandler("template", MakeShared<XMLNodeHandlerTemplate>());
+	XMLParser::RegisterNodeHandler("include", MakeShared<XMLNodeHandlerInclude>());
 
 
 	// XML node handlers for control elements
 	// XML node handlers for control elements
 	XMLParser::RegisterNodeHandler("datagrid", MakeShared<XMLNodeHandlerDataGrid>());
 	XMLParser::RegisterNodeHandler("datagrid", MakeShared<XMLNodeHandlerDataGrid>());

+ 17 - 5
Source/Core/XMLNodeHandlerBody.cpp

@@ -48,6 +48,23 @@ Element* XMLNodeHandlerBody::ElementStart(XMLParser* parser, const String& RMLUI
 
 
 	Element* element = parser->GetParseFrame()->element;
 	Element* element = parser->GetParseFrame()->element;
 
 
+	// Apply any attributes to the document, but only if the current element is the root of the current document,
+	// which should only hold for body elements, but not for included templates.
+	ElementDocument* document = parser->GetParseFrame()->element->GetOwnerDocument();
+	if (document && document == element)
+	{
+		for (auto& pair : attributes) 
+		{
+			auto attribute = document->GetAttribute(pair.first);
+			if(attribute && *attribute != pair.second)
+			{
+				Log::Message(Log::LT_WARNING, "Overriding attribute '%s' in element %s during template insertion.", pair.first.c_str(), element->GetAddress().c_str());
+			}
+
+			document->SetAttribute(pair.first, pair.second);
+		}
+	}
+
 	// Check for and apply any template
 	// Check for and apply any template
 	String template_name = Get<String>(attributes, "template", "");
 	String template_name = Get<String>(attributes, "template", "");
 	if (!template_name.empty())
 	if (!template_name.empty())
@@ -55,11 +72,6 @@ Element* XMLNodeHandlerBody::ElementStart(XMLParser* parser, const String& RMLUI
 		element = XMLParseTools::ParseTemplate(element, template_name);
 		element = XMLParseTools::ParseTemplate(element, template_name);
 	}
 	}
 
 
-	// Apply any attributes to the document
-	ElementDocument* document = parser->GetParseFrame()->element->GetOwnerDocument();
-	if (document)
-		document->SetAttributes(attributes);
-
 	// Tell the parser to use the element handler for all children
 	// Tell the parser to use the element handler for all children
 	parser->PushDefaultHandler();
 	parser->PushDefaultHandler();
 	
 	

+ 137 - 0
Tests/Source/UnitTests/Template.cpp

@@ -0,0 +1,137 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../Common/TestsShell.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <doctest.h>
+#include <algorithm>
+
+using namespace Rml;
+
+static const String document_template_rml = R"(
+<rml>
+<head>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<link type="text/template" href="/assets/window.rml"/>
+	<style>
+		body.window
+		{
+			top: 100px;
+			left: 200px;
+			width: 1300px;
+			height: 450px;
+		}
+	</style>
+</head>
+
+<body id="body" template="window">
+<p id="p">A paragraph</p>
+</body>
+</rml>
+)";
+
+static const String p_address_template = "p#p < div#content < div#window < body#body.window < #root#main";
+
+static const String document_include_rml = R"(
+<rml>
+<head>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<link type="text/template" href="/assets/window.rml"/>
+	<style>
+		body.window
+		{
+			top: 100px;
+			left: 200px;
+			width: 1300px;
+			height: 450px;
+		}
+	</style>
+</head>
+
+<body id="body">
+<p>Paragraph outside the window.</p>
+<div id="include">
+	<include template="window">
+		<p id="p">A paragraph</p>
+	</include>
+</div>
+</body>
+</rml>
+)";
+
+static const String p_address_include = "p#p < div#content < div#window < div#include < body#body < #root#main";
+
+
+
+TEST_CASE("template.body")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	SUBCASE("body")
+	{
+		ElementDocument* document = context->LoadDocumentFromMemory(document_template_rml);
+		REQUIRE(document);
+		document->Show();
+
+		context->Update();
+		context->Render();
+
+		TestsShell::RenderLoop();
+
+		Element* el_p = document->GetElementById("p");
+		REQUIRE(el_p);
+
+		CHECK(el_p->GetAddress() == p_address_template);
+
+		document->Close();
+	}
+
+	SUBCASE("include")
+	{
+		ElementDocument* document = context->LoadDocumentFromMemory(document_include_rml);
+		REQUIRE(document);
+		document->Show();
+
+		context->Update();
+		context->Render();
+
+		TestsShell::RenderLoop();
+
+		Element* el_p = document->GetElementById("p");
+		REQUIRE(el_p);
+
+		CHECK(el_p->GetAddress() == p_address_include);
+
+		document->Close();
+	}
+
+	TestsShell::ShutdownShell();
+}