From 95727dee43f0b002f28b88ba806a552499e6b519 Mon Sep 17 00:00:00 2001 From: Alexander Raszka Date: Sat, 24 Feb 2024 13:17:46 +0100 Subject: [PATCH 1/2] Add fallthrough attributes - Add test for fallthrough attributes - Remove unused MtContextAlias --- src/ManiaTemplates/Components/MtComponent.cs | 1 - .../ControlElements/MtContextAlias.cs | 17 --- src/ManiaTemplates/Lib/MtTransformer.cs | 115 +++++++++++++----- .../IntegrationTests/ManialinkEngineTest.cs | 15 +++ .../expected/fallthrough-test.xml | 8 ++ .../templates/fallthrough-attributes.mt | 9 ++ .../templates/slots/window.mt | 3 - 7 files changed, 116 insertions(+), 52 deletions(-) delete mode 100644 src/ManiaTemplates/ControlElements/MtContextAlias.cs create mode 100644 tests/ManiaTemplates.Tests/IntegrationTests/expected/fallthrough-test.xml create mode 100644 tests/ManiaTemplates.Tests/IntegrationTests/templates/fallthrough-attributes.mt diff --git a/src/ManiaTemplates/Components/MtComponent.cs b/src/ManiaTemplates/Components/MtComponent.cs index 8f3bcb0..5a53e0b 100644 --- a/src/ManiaTemplates/Components/MtComponent.cs +++ b/src/ManiaTemplates/Components/MtComponent.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Security; using System.Xml; using ManiaTemplates.Exceptions; using ManiaTemplates.Lib; diff --git a/src/ManiaTemplates/ControlElements/MtContextAlias.cs b/src/ManiaTemplates/ControlElements/MtContextAlias.cs deleted file mode 100644 index 59d73e6..0000000 --- a/src/ManiaTemplates/ControlElements/MtContextAlias.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace ManiaTemplates.ControlElements; - -public class MtContextAlias -{ - public MtDataContext Context { get; init; } - public Dictionary Aliases { get; } = new(); - - public MtContextAlias(MtDataContext context) - { - Context = context; - foreach (var (variableName, variableType) in Context) - { - var variableAlias = variableName + (new Random()).Next(); - Aliases[variableName] = variableAlias; - } - } -} \ No newline at end of file diff --git a/src/ManiaTemplates/Lib/MtTransformer.cs b/src/ManiaTemplates/Lib/MtTransformer.cs index 5f3bf67..57ff5cd 100644 --- a/src/ManiaTemplates/Lib/MtTransformer.cs +++ b/src/ManiaTemplates/Lib/MtTransformer.cs @@ -1,6 +1,7 @@ using System.CodeDom; using System.Dynamic; using System.Text; +using System.Text.RegularExpressions; using System.Xml; using ManiaTemplates.Components; using ManiaTemplates.ControlElements; @@ -146,7 +147,8 @@ private string CreateTemplatePropertiesBlock(MtComponent mtComponent) /// Process a ManiaTemplate node. /// private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataContext oldContext, - MtComponent rootComponent, MtComponent parentComponent) + MtComponent rootComponent, MtComponent parentComponent, + Dictionary? fallthroughAttributesMap = null) { Snippet snippet = []; @@ -166,13 +168,13 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont currentContext = forEachCondition.Context; _loopDepth++; } - + if (componentMap.TryGetValue(tag, out var importedComponent)) { //Node is a component var component = engine.GetComponent(importedComponent.TemplateKey); var slotContents = GetSlotContentsGroupedBySlotName(childNode, component, componentMap, currentContext, - parentComponent, rootComponent); + parentComponent, rootComponent); var oldLoopDepth = _loopDepth; _loopDepth = 0; @@ -181,16 +183,11 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont component, parentComponent, currentContext, + oldContext, attributeList, - ProcessNode( - IXmlMethods.NodeFromString(component.TemplateContent), - componentMap.Overload(component.ImportedComponents), - oldContext, - rootComponent: rootComponent, - parentComponent: component - ), slotContents, - rootComponent: rootComponent + rootComponent, + componentMap ); _loopDepth = oldLoopDepth; @@ -219,6 +216,26 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont default: { var hasChildren = childNode.HasChildNodes; + + if (fallthroughAttributesMap != null && node.ChildNodes.Count == 1) + { + foreach (var (originalAttributeName, aliasAttributeName) in fallthroughAttributesMap) + { + if (attributeList.ContainsKey(originalAttributeName)) + { + //Overwrite existing attribute with fallthrough value + attributeList[originalAttributeName] = + maniaTemplateLanguage.InsertResult(aliasAttributeName); + } + else + { + //Resolve alias on node + attributeList.Add(originalAttributeName, + maniaTemplateLanguage.InsertResult(aliasAttributeName)); + } + } + } + subSnippet.AppendLine(IXmlMethods.CreateOpeningTag(tag, attributeList, hasChildren, curlyContentWrapper: maniaTemplateLanguage.InsertResult)); @@ -313,10 +330,11 @@ private string ProcessComponentNode( MtComponent component, MtComponent parentComponent, MtDataContext currentContext, + MtDataContext oldContext, MtComponentAttributes attributeList, - string componentBody, - IReadOnlyDictionary slotContents, - MtComponent rootComponent + Dictionary slotContents, + MtComponent rootComponent, + MtComponentMap componentMap ) { foreach (var slotName in component.Slots) @@ -342,15 +360,9 @@ MtComponent rootComponent }); } + var templateContentNode = IXmlMethods.NodeFromString(component.TemplateContent); var renderMethodName = GetComponentRenderMethodName(component, currentContext); - var contextAliasMap = new MtContextAlias(currentContext); - if (!_renderMethods.ContainsKey(renderMethodName)) - { - _renderMethods.Add( - renderMethodName, - CreateComponentRenderMethod(component, renderMethodName, componentBody, contextAliasMap) - ); - } + var fallthroughAttributesAliasMap = new Dictionary(); //Create render call var renderComponentCall = new StringBuilder(renderMethodName + "("); @@ -361,15 +373,34 @@ MtComponent rootComponent //Attach attributes to render method call foreach (var (attributeName, attributeValue) in attributeList) { + bool isStringType; + string attributeNameAlias; + //Skip attributes that don't match component property name - if (!component.Properties.TryGetValue(attributeName, out var componentProperty)) continue; + if (component.Properties.TryGetValue(attributeName, out var componentProperty)) + { + isStringType = componentProperty.IsStringType(); + attributeNameAlias = attributeName; + } + else + { + if (templateContentNode.ChildNodes.Count != 1) + { + //Only add fallthrough attributes if the component template has only one root element + continue; + } + + isStringType = true; + attributeNameAlias = GetFallthroughAttributeAlias(attributeName); + fallthroughAttributesAliasMap[attributeName] = attributeNameAlias; + } - var methodArgument = componentProperty.IsStringType() + var methodArgument = isStringType ? IStringMethods.WrapStringInQuotes( ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"{{({s})}}")) : ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"({s})"); - componentRenderArguments.Add(CreateMethodCallArgument(attributeName, methodArgument)); + componentRenderArguments.Add(CreateMethodCallArgument(attributeNameAlias, methodArgument)); } renderComponentCall.Append(string.Join(", ", componentRenderArguments)); @@ -423,6 +454,23 @@ MtComponent rootComponent _namespaces.AddRange(component.Namespaces); + if (!_renderMethods.ContainsKey(renderMethodName)) + { + var componentBody = ProcessNode( + templateContentNode, + componentMap.Overload(component.ImportedComponents), + oldContext, + rootComponent: rootComponent, + parentComponent: component, + fallthroughAttributesMap: fallthroughAttributesAliasMap + ); + + _renderMethods.Add( + renderMethodName, + CreateComponentRenderMethod(component, renderMethodName, componentBody, fallthroughAttributesAliasMap) + ); + } + return renderComponentCall.ToString(); } @@ -430,17 +478,14 @@ MtComponent rootComponent /// Creates the method which renders the contents of a component. /// private string CreateComponentRenderMethod(MtComponent component, string renderMethodName, string componentBody, - MtContextAlias contextAliasMap) + Dictionary aliasMap) { //open method arguments var arguments = new List(); var body = new StringBuilder(componentBody); - //Add local variables to component render method call (loop index, fallthrough vars, ...) - // foreach (var (localVariableName, localVariableType) in contextAliasMap.Context) - // { - // arguments.Add($"{localVariableType} {contextAliasMap.Aliases[localVariableName]}"); - // } + //Add fallthrough variables to component render method call + arguments.AddRange(aliasMap.Values.Select(aliasAttributeName => $"string {aliasAttributeName}")); //add slot render methods AppendSlotRenderArgumentsToList(component, arguments); @@ -663,6 +708,14 @@ private static string GetComponentRenderMethodName(MtComponent component, MtData return $"Render_Component_{component.Id()}{context}"; } + /// + /// Returns a valid variable name alias. + /// + private static string GetFallthroughAttributeAlias(string variableName) + { + return Regex.Replace(variableName, @"\W", "") + new Random().Next(); + } + /// /// Returns the method name that renders the scripts of a given component. /// diff --git a/tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs b/tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs index b2c0e53..706f575 100644 --- a/tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs +++ b/tests/ManiaTemplates.Tests/IntegrationTests/ManialinkEngineTest.cs @@ -158,4 +158,19 @@ public async void Component_Properties_Should_Not_Collide_With_Loop_Variables() var template = _maniaTemplateEngine.RenderAsync("LoopTest", new { }, assemblies).Result; Assert.Equal(expected, template, ignoreLineEndingDifferences: true); } + + [Fact] + public async void Should_Pass_Unmapped_Node_Attributes_To_Component_Root_Element() + { + var wrapperComponent = await File.ReadAllTextAsync("IntegrationTests/templates/wrapper.mt"); + var fallthroughAttributesTestComponent = await File.ReadAllTextAsync("IntegrationTests/templates/fallthrough-attributes.mt"); + var expected = await File.ReadAllTextAsync("IntegrationTests/expected/fallthrough-test.xml"); + var assemblies = new[] { typeof(ManiaTemplateEngine).Assembly, typeof(ComplexDataType).Assembly }; + + _maniaTemplateEngine.AddTemplateFromString("Wrapper", wrapperComponent); + _maniaTemplateEngine.AddTemplateFromString("FallthroughTest", fallthroughAttributesTestComponent); + + var template = _maniaTemplateEngine.RenderAsync("FallthroughTest", new { }, assemblies).Result; + Assert.Equal(expected, template, ignoreLineEndingDifferences: true); + } } \ No newline at end of file diff --git a/tests/ManiaTemplates.Tests/IntegrationTests/expected/fallthrough-test.xml b/tests/ManiaTemplates.Tests/IntegrationTests/expected/fallthrough-test.xml new file mode 100644 index 0000000..4b75080 --- /dev/null +++ b/tests/ManiaTemplates.Tests/IntegrationTests/expected/fallthrough-test.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/ManiaTemplates.Tests/IntegrationTests/templates/fallthrough-attributes.mt b/tests/ManiaTemplates.Tests/IntegrationTests/templates/fallthrough-attributes.mt new file mode 100644 index 0000000..395f8a7 --- /dev/null +++ b/tests/ManiaTemplates.Tests/IntegrationTests/templates/fallthrough-attributes.mt @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/tests/ManiaTemplates.Tests/IntegrationTests/templates/slots/window.mt b/tests/ManiaTemplates.Tests/IntegrationTests/templates/slots/window.mt index 5a9a4c6..ff1f279 100644 --- a/tests/ManiaTemplates.Tests/IntegrationTests/templates/slots/window.mt +++ b/tests/ManiaTemplates.Tests/IntegrationTests/templates/slots/window.mt @@ -13,9 +13,6 @@ y="-{{ padding }}" width="{{ width }}" height="{{ height }}" - - pos="{{ padding }} -{{ padding }}" - size="{{ width - padding * 2 }} {{ height - padding * 2 }}" >