Skip to content

Commit

Permalink
Merge pull request #60 from EvoEsports/feature/escaped-expressions
Browse files Browse the repository at this point in the history
Feature/escaped expressions
  • Loading branch information
araszka authored Jun 8, 2024
2 parents 148754a + 748bc1d commit 2f8fdae
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 52 deletions.
8 changes: 8 additions & 0 deletions src/ManiaTemplates/Components/MtComponentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ManiaTemplates.Components;

public class MtComponentAttribute
{
public required string Value { get; init; }
public bool IsFallthroughAttribute { get; init; }
public string? Alias { get; init; }
}
4 changes: 2 additions & 2 deletions src/ManiaTemplates/Components/MtComponentAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace ManiaTemplates.Components;

public class MtComponentAttributes : Dictionary<string, string>
public class MtComponentAttributes : Dictionary<string, MtComponentAttribute>
{
/// <summary>
/// Checks the attribute list for if-condition and if found removes & returns it, else null.
Expand Down Expand Up @@ -34,6 +34,6 @@ public string Pull(string name)
{
var value = this[name];
Remove(name);
return value;
return value.Value;
}
}
31 changes: 27 additions & 4 deletions src/ManiaTemplates/Interfaces/ICurlyBraceMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,40 @@ namespace ManiaTemplates.Interfaces;
public interface ICurlyBraceMethods
{
protected static readonly Regex TemplateInterpolationRegex = new(@"\{\{\s*(.+?)\s*\}\}");

protected static readonly Regex TemplateRawInterpolationRegex = new(@"\{!\s*(.+?)\s*!\}");

/// <summary>
/// Takes the contents of double curly braces in a string and wraps them into something else.
/// The second argument is a function that takes a string-argument and returns the newly wrapped string.
/// The third argument is a function that takes a string-argument and returns the newly wrapped (escaped) string.
/// </summary>
public static string ReplaceCurlyBracesWithRawOutput(
string value,
Func<string, string> curlyContentWrapper,
Func<string, string> curlyRawContentWrapper
)
{
var output = value;
output = ReplaceCurlyBraces(output, curlyRawContentWrapper, TemplateRawInterpolationRegex);
output = ReplaceCurlyBraces(output, curlyContentWrapper);

return output;
}

/// <summary>
/// Takes the contents of double curly braces in a string and wraps them into something else. The second Argument takes a string-argument and returns the newly wrapped string.
/// Takes the contents of double curly braces in a string and wraps them into something else.
/// The second argument takes a string-argument and returns the newly wrapped string.
/// The third argument is an optional regex pattern to find substrings to replace.
/// </summary>
public static string ReplaceCurlyBraces(string value, Func<string, string> curlyContentWrapper)
public static string ReplaceCurlyBraces(string value, Func<string, string> curlyContentWrapper,
Regex? interpolationPattern = null)
{
PreventCurlyBraceCountMismatch(value);
PreventInterpolationRecursion(value);

var matches = TemplateInterpolationRegex.Match(value);
var output = value;
var pattern = interpolationPattern ?? TemplateInterpolationRegex;
var matches = pattern.Match(value);

while (matches.Success)
{
Expand Down
2 changes: 1 addition & 1 deletion src/ManiaTemplates/Interfaces/IManiaTemplateLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public interface IManiaTemplateLanguage
{
public string Context(string content);
public string InsertResult(string content);
public string Code(string content);
public string InsertResultEscaped(string content);
public Snippet FeatureBlock(string content);
public string FeatureBlockStart();
public string FeatureBlockEnd();
Expand Down
42 changes: 31 additions & 11 deletions src/ManiaTemplates/Interfaces/IXmlMethods.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Xml;
using System.Text;
using System.Xml;
using ManiaTemplates.Components;

namespace ManiaTemplates.Interfaces;

public interface IXmlMethods: ICurlyBraceMethods
public interface IXmlMethods : ICurlyBraceMethods
{
/// <summary>
/// Parses the attributes of a XmlNode to an MtComponentAttributes-instance.
Expand All @@ -15,30 +16,49 @@ public static MtComponentAttributes GetAttributes(XmlNode node)

foreach (XmlAttribute attribute in node.Attributes)
{
attributeList.Add(attribute.Name, attribute.Value);
attributeList.Add(attribute.Name, new MtComponentAttribute
{
Value = attribute.Value
});
}

return attributeList;
}

/// <summary>
/// Creates a xml opening tag for the given string and attribute list.
/// </summary>
public static string CreateOpeningTag(string tag, MtComponentAttributes attributeList, bool hasChildren, Func<string, string> curlyContentWrapper)
public static string CreateOpeningTag(string tag, MtComponentAttributes attributeList, bool hasChildren,
Func<string, string> curlyContentWrapper)
{
var output = $"<{tag}";
var tagParts = new List<string>
{
$"<{tag}"
};

foreach (var (attributeName, attributeValue) in attributeList)
foreach (var (attributeName, attribute) in attributeList)
{
output += @$" {attributeName}=""{ReplaceCurlyBraces(attributeValue, curlyContentWrapper)}""";
var newAttribute = @$"{attributeName}=""{ReplaceCurlyBraces(attribute.Value, curlyContentWrapper)}""";

if (!attribute.IsFallthroughAttribute)
{
tagParts.Add(newAttribute);
continue;
}

tagParts.Add(new StringBuilder()
.Append($"<#+ if({attribute.Alias} != null){{ #>")
.Append(newAttribute)
.Append("<#+ } #>")
.ToString());
}

if (!hasChildren)
{
output += " /";
tagParts.Add("/");
}

return output + ">";
return string.Join(" ", tagParts) + ">";
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/ManiaTemplates/Languages/MtLanguageT4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace ManiaTemplates.Languages;

public class MtLanguageT4 : IManiaTemplateLanguage
{
private static readonly Regex TemplateFeatureControlRegex = new(@"#>\s*<#\+");
private static readonly Regex TemplateFeatureControlRegex = new(@"#>[\n]*<#\+");

public string Context(string content)
{
return $"<#@ {content} #>";
Expand All @@ -18,9 +18,9 @@ public string InsertResult(string content)
return $"<#= ({content}) #>";
}

public string Code(string content)
public string InsertResultEscaped(string content)
{
return $"<# {content} #>";
return $"<#= Security.Escape({content}) #>";
}

public Snippet FeatureBlock(string content)
Expand Down
9 changes: 7 additions & 2 deletions src/ManiaTemplates/Lib/MtScriptTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ public string CreateManiaScriptBlock(MtComponent component)
.AppendLine(templateLanguage.FeatureBlockEnd());
}

renderMethod.AppendLine(ICurlyBraceMethods.ReplaceCurlyBraces(ExtractManiaScriptDirectives(script.Content),
templateLanguage.InsertResult));
renderMethod.AppendLine(
ICurlyBraceMethods.ReplaceCurlyBracesWithRawOutput(
ExtractManiaScriptDirectives(script.Content),
templateLanguage.InsertResultEscaped,
templateLanguage.InsertResult
)
);

if (script.Once)
{
Expand Down
2 changes: 1 addition & 1 deletion src/ManiaTemplates/Lib/MtSpecialCharEscaper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public abstract class MtSpecialCharEscaper
public static readonly Regex XmlTagAttributeMatcherSingleQuote = new("[\\w-]+='(.+?)'");

/// <summary>
/// Takes a XML string and escapes all special chars in node attributes.
/// Takes an XML string and escapes all special chars in node attributes.
/// </summary>
public static string EscapeXmlSpecialCharsInAttributes(string inputXmlString)
{
Expand Down
39 changes: 22 additions & 17 deletions src/ManiaTemplates/Lib/MtTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public string BuildManialink(MtComponent rootComponent, string className, int ve

var template = new Snippet
{
maniaTemplateLanguage.Context(@"template language=""C#"""), //Might not be needed
maniaTemplateLanguage.Context(@"import namespace=""System.Collections.Generic"""),
maniaTemplateLanguage.Context("import namespace=\"System.Collections.Generic\""),
maniaTemplateLanguage.Context("import namespace=\"ManiaTemplates.Lib\""),
CreateImportStatements(),
ManiaLink.OpenTag(className, version, rootComponent.DisplayLayer),
"<#",
Expand Down Expand Up @@ -224,20 +224,26 @@ private string ProcessNode(XmlNode node, MtComponentMap componentMap, MtDataCont
if (attributeList.ContainsKey(originalAttributeName))
{
//Overwrite existing attribute with fallthrough value
attributeList[originalAttributeName] =
maniaTemplateLanguage.InsertResult(aliasAttributeName);
attributeList[originalAttributeName] = new MtComponentAttribute
{
Value = maniaTemplateLanguage.InsertResultEscaped(aliasAttributeName)
};
}
else
{
//Resolve alias on node
attributeList.Add(originalAttributeName,
maniaTemplateLanguage.InsertResult(aliasAttributeName));
attributeList.Add(originalAttributeName, new MtComponentAttribute
{
Value = maniaTemplateLanguage.InsertResultEscaped(aliasAttributeName),
Alias = aliasAttributeName,
IsFallthroughAttribute = true
});
}
}
}

subSnippet.AppendLine(IXmlMethods.CreateOpeningTag(tag, attributeList, hasChildren,
curlyContentWrapper: maniaTemplateLanguage.InsertResult));
curlyContentWrapper: maniaTemplateLanguage.InsertResultEscaped));

if (hasChildren)
{
Expand Down Expand Up @@ -371,7 +377,7 @@ MtComponentMap componentMap
var componentRenderArguments = new List<string>();

//Attach attributes to render method call
foreach (var (attributeName, attributeValue) in attributeList)
foreach (var (attributeName, attribute) in attributeList)
{
bool isStringType;
string attributeNameAlias;
Expand All @@ -389,16 +395,16 @@ MtComponentMap componentMap
//Only add fallthrough attributes if the component template has only one root element
continue;
}

isStringType = true;
attributeNameAlias = GetFallthroughAttributeAlias(attributeName);
fallthroughAttributesAliasMap[attributeName] = attributeNameAlias;
}

var methodArgument = isStringType
? IStringMethods.WrapStringInQuotes(
ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"{{({s})}}"))
: ICurlyBraceMethods.ReplaceCurlyBraces(attributeValue, s => $"({s})");
ICurlyBraceMethods.ReplaceCurlyBraces(attribute.Value, s => $"{{({s})}}"))
: ICurlyBraceMethods.ReplaceCurlyBraces(attribute.Value, s => $"({s})");

componentRenderArguments.Add(CreateMethodCallArgument(attributeNameAlias, methodArgument));
}
Expand Down Expand Up @@ -484,15 +490,15 @@ private string CreateComponentRenderMethod(MtComponent component, string renderM
var arguments = new List<string>();
var body = new StringBuilder(componentBody);

//Add fallthrough variables to component render method call
arguments.AddRange(aliasMap.Values.Select(aliasAttributeName => $"string {aliasAttributeName}"));

//add slot render methods
AppendSlotRenderArgumentsToList(component, arguments);

//add component properties as arguments with defaults
AppendComponentPropertiesArgumentsList(component, arguments);

//Add fallthrough variables to component render method call
arguments.AddRange(aliasMap.Values.Select(aliasAttributeName => $"string? {aliasAttributeName} = null"));

//Insert mania scripts
if (component.Scripts.Count > 0)
{
Expand Down Expand Up @@ -523,7 +529,6 @@ private string CreateSlotRenderMethod(int scope, MtDataContext context, string s
{
var methodArguments = new List<string>();
var methodName = GetSlotRenderMethodName(scope, slotName);
var localVariables = new List<string>();

//Add slot render methods.
AppendSlotRenderArgumentsToList(parentComponent, methodArguments);
Expand All @@ -543,7 +548,7 @@ private string CreateSlotRenderMethod(int scope, MtDataContext context, string s
AppendComponentPropertiesArgumentsList(parentComponent, methodArguments);
}

return CreateRenderMethod(methodName, methodArguments, slotContent, localVariables);
return CreateRenderMethod(methodName, methodArguments, slotContent);
}

/// <summary>
Expand Down Expand Up @@ -713,7 +718,7 @@ private static string GetComponentRenderMethodName(MtComponent component, MtData
/// </summary>
private static string GetFallthroughAttributeAlias(string variableName)
{
return Regex.Replace(variableName, @"\W", "") + new Random().Next();
return "FT_" + Regex.Replace(variableName.Replace("-", "HYPH"), @"\W", "");
}

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions src/ManiaTemplates/Lib/Security.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Globalization;
using System.Security;

namespace ManiaTemplates.Lib;

public abstract class Security
{
/// <summary>
/// Converts any given input to an XML safe string.
/// </summary>
public static string Escape(dynamic input)
{
return SecurityElement.Escape(Convert.ToString(input, CultureInfo.InvariantCulture))
.Replace("--", "&#45;&#45;");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ public class MtComponentAttributesTest
[Fact]
public void Should_Contain_Single_Attribute_Pair()
{
_mtComponentAttributes["key"] = "value";
_mtComponentAttributes["key"] = new MtComponentAttribute{Value = "value"};
Assert.Single(_mtComponentAttributes);
}

[Fact]
public void Should_Remove_Existing()
{
_mtComponentAttributes["key"] = "value";
_mtComponentAttributes["key"] = new MtComponentAttribute{Value = "value"};
var result = _mtComponentAttributes.Pull("key");

Assert.Equal("value", result);
Expand Down
8 changes: 4 additions & 4 deletions tests/ManiaTemplates.Tests/Lib/MtTransformerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void Should_Join_Feature_Blocks()
var language = new MtLanguageT4();

Assert.Equal("<#+\n unittest \n#>",
language.OptimizeOutput("<#+#><#+\n #> <#+ unittest \n#><#+ \n\n\n#>"));
language.OptimizeOutput("<#+#><#+\n #>\n<#+ unittest \n#><#+ \n\n\n#>"));
}

[Fact]
Expand All @@ -166,7 +166,7 @@ public void Should_Create_Xml_Opening_Tag()
Assert.Equal("<test />", IXmlMethods.CreateOpeningTag("test", attributeList, false, lang.InsertResult));
Assert.Equal("<test>", IXmlMethods.CreateOpeningTag("test", attributeList, true, lang.InsertResult));

attributeList["prop"] = "value";
attributeList["prop"] = new MtComponentAttribute{Value = "value"};
Assert.Equal("""<test prop="value" />""", IXmlMethods.CreateOpeningTag("test", attributeList, false, lang.InsertResult));
Assert.Equal("""<test prop="value">""", IXmlMethods.CreateOpeningTag("test", attributeList, true, lang.InsertResult));
}
Expand All @@ -188,8 +188,8 @@ public void Should_Convert_Xml_Node_Arguments_To_MtComponentAttributes_Instance(

var attributes = IXmlMethods.GetAttributes(node.FirstChild);
Assert.Equal(2, attributes.Count);
Assert.Equal("test1", attributes["arg1"]);
Assert.Equal("test2", attributes["arg2"]);
Assert.Equal("test1", attributes["arg1"].Value);
Assert.Equal("test2", attributes["arg2"].Value);
}

[Fact]
Expand Down
Loading

0 comments on commit 2f8fdae

Please sign in to comment.