Skip to content

Commit

Permalink
Merge pull request #8 from askonomm/7-implement-comparison-expression…
Browse files Browse the repository at this point in the history
…s-in-if-and-unless

Implement expression validator.
  • Loading branch information
askonomm authored Oct 17, 2024
2 parents 731c0b3 + 16dcc67 commit b289505
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 84 deletions.
4 changes: 2 additions & 2 deletions Htmt/AttributeParsers/ForAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class ForAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:for]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand All @@ -31,7 +31,7 @@ public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList?

foreach (var item in enumerable)
{
var iterationData = new Dictionary<string, object>(data);
var iterationData = new Dictionary<string, object?>(data);

if (!string.IsNullOrEmpty(asVar))
{
Expand Down
2 changes: 1 addition & 1 deletion Htmt/AttributeParsers/GenericValueAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class GenericValueAttributeParser : IAttributeParser
{
public string XTag => "//*[@*[starts-with(name(), 'x:')]]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand Down
57 changes: 37 additions & 20 deletions Htmt/AttributeParsers/IfAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class IfAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:if]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand All @@ -21,30 +21,47 @@ public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList?

var key = n.GetAttribute("x:if");
n.RemoveAttribute("x:if");
var value = Helper.FindValueByKeys(data, key.Split('.'));

// Remove node if value is null
if(value == null)
// if key is a single word, we just check for a truthy value
if (!key.Contains(' '))
{
n.ParentNode?.RemoveChild(n);
continue;
var value = Helper.FindValueByKeys(data, key.Split('.'));

// Remove node if value is null
if (value == null)
{
n.ParentNode?.RemoveChild(n);
continue;
}

// Remove node if value is falsey
var removeNode = value switch
{
bool b => !b,
int i => i == 0,
double d => d == 0,
string s => string.IsNullOrEmpty(s),
IEnumerable<object> e => !e.Any(),
IDictionary d => d.Count == 0,
_ => false
};

if (removeNode)
{
n.ParentNode?.RemoveChild(n);
}
}

// Remove node if value is falsey
var removeNode = value switch
// if key contains multiple words, evaluate the expression with ExpressionValidator
else
{
bool b => !b,
int i => i == 0,
double d => d == 0,
string s => string.IsNullOrEmpty(s),
IEnumerable<object> e => !e.Any(),
IDictionary d => d.Count == 0,
_ => false
};

if (removeNode)
{
n.ParentNode?.RemoveChild(n);
var expression = new ExpressionValidator(key);
var result = expression.Validates(data);

if (!result)
{
n.ParentNode?.RemoveChild(n);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Htmt/AttributeParsers/InnerHtmlAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class InnerHtmlAttributeParser: IAttributeParser
{
public string XTag => "//*[@x:inner-html]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand Down
2 changes: 1 addition & 1 deletion Htmt/AttributeParsers/InnerTextAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class InnerTextAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:inner-text]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand Down
2 changes: 1 addition & 1 deletion Htmt/AttributeParsers/OuterHtmlAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class OuterHtmlAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:outer-html]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand Down
2 changes: 1 addition & 1 deletion Htmt/AttributeParsers/OuterTextAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public partial class OuterTextAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:outer-text]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand Down
47 changes: 32 additions & 15 deletions Htmt/AttributeParsers/UnlessAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class UnlessAttributeParser : IAttributeParser
{
public string XTag => "//*[@x:unless]";

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes)
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
Expand All @@ -21,22 +21,39 @@ public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList?

var key = n.GetAttribute("x:unless");
n.RemoveAttribute("x:unless");
var value = Helper.FindValueByKeys(data, key.Split('.'));

var removeNode = value switch

// if key is a single word, we just check for a truthy value
if (!key.Contains(' '))
{
bool b => b,
int i => i != 0,
double d => d != 0,
string s => !string.IsNullOrEmpty(s),
IEnumerable<object> e => e.Any(),
IDictionary d => d.Count > 0,
_ => true
};

if (removeNode)
var value = Helper.FindValueByKeys(data, key.Split('.'));

var removeNode = value switch
{
bool b => b,
int i => i != 0,
double d => d != 0,
string s => !string.IsNullOrEmpty(s),
IEnumerable<object> e => e.Any(),
IDictionary d => d.Count > 0,
_ => true
};

if (removeNode)
{
n.ParentNode?.RemoveChild(n);
}
}

// if key contains multiple words, evaluate the expression with ExpressionValidator
else
{
n.ParentNode?.RemoveChild(n);
var expression = new ExpressionValidator(key);
var result = expression.Validates(data);

if (result)
{
n.ParentNode?.RemoveChild(n);
}
}
}
}
Expand Down
118 changes: 118 additions & 0 deletions Htmt/ExpressionValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System.Data;
using System.Text.RegularExpressions;

namespace Htmt;

public class ExpressionValidator(string expression)
{
public bool Validates(Dictionary<string, object?> data)
{
return EvaluateExp(Normalize(data));
}

private string Normalize(Dictionary<string, object?> data)
{
var evaluatedExpression = expression;
var keyMatches = Regex.Matches(expression, """'(?<string>[^']*)'|(?<key>\w+)""");

foreach (Match match in keyMatches)
{
// if this is not a key, skip
if (match.Groups["key"].Success == false) continue;

var key = match.Groups["key"].Value;

// skip if the key is an operator
if (key is "is" or "and" or "or" or "not") continue;

// skip if key is integer, double, float, boolean, null or string
if (int.TryParse(key, out _)) continue;
if (double.TryParse(key, out _)) continue;
if (float.TryParse(key, out _)) continue;
if (bool.TryParse(key, out _)) continue;
if (key == "null") continue;

var value = Helper.FindValueByKeys(data, key.Split('.'));
var regex = new Regex($@"{key}");

// if value is bool, lowercase it
if (value is bool b)
{
evaluatedExpression = regex.Replace(evaluatedExpression, b.ToString().ToLower(), 1);
continue;
}

evaluatedExpression = regex.Replace(evaluatedExpression, value?.ToString() ?? "null", 1);
}

// remove single quotes from string values
return Regex.Replace(evaluatedExpression, "'(.*?)'", "$1");
}

private static bool EvaluateExp(string exp)
{
while (exp.Contains('('))
{
var openIndex = exp.LastIndexOf('(');
var closeIndex = exp.IndexOf(')', openIndex);

if (openIndex == -1 || closeIndex == -1)
{
throw new SyntaxErrorException("Invalid expression");
}

var subExp = exp.Substring(openIndex + 1, closeIndex - openIndex - 1);
var subResult = EvaluateSimpleExp(subExp);

exp = exp.Substring(0, openIndex) + subResult.ToString().ToLower() + exp.Substring(closeIndex + 1);
}

return EvaluateSimpleExp(exp);
}

private static bool EvaluateSimpleExp(string exp)
{
var orParts = exp.Split(new[] { " or " }, StringSplitOptions.None);

foreach (var orPart in orParts)
{
var andParts = orPart.Split([" and "], StringSplitOptions.None);
var andResult = true;

foreach (var andPart in andParts)
{
andResult &= EvaluateCondition(andPart.Trim());
}

if (andResult) return true;
}

return false;
}

private static bool EvaluateCondition(string condition)
{
if (condition == "true") return true;
if (condition == "false") return false;

if (condition.Contains(" is "))
{
var parts = condition.Split([" is "], StringSplitOptions.None);
var key = parts[0].Trim();
var value = parts[1].Trim();

return key == value;
}

if (condition.Contains(" not "))
{
var parts = condition.Split([" not "], StringSplitOptions.None);
var key = parts[0].Trim();
var value = parts[1].Trim();

return key != value;
}

throw new SyntaxErrorException("Invalid condition");
}
}
20 changes: 10 additions & 10 deletions Htmt/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Htmt;

public partial class Helper
{
public static object? FindValueByKeys(Dictionary<string, object> data, string[] keys)
public static object? FindValueByKeys(Dictionary<string, object?> data, string[] keys)
{
while (true)
{
Expand All @@ -22,35 +22,35 @@ public partial class Helper

switch (v)
{
case Dictionary<string, object> dict:
case Dictionary<string, object?> dict:
{
var newKeys = keys.Skip(1).ToArray();

data = dict;
keys = newKeys;
continue;
}
case Dictionary<string, string> dict:
case Dictionary<string, string?> dict:
{
var newKeys = keys.Skip(1).ToArray();

data = dict.ToDictionary(x => x.Key, x => (object)x.Value);
data = dict.ToDictionary(x => x.Key, object? (x) => x.Value);
keys = newKeys;
continue;
}
case Dictionary<string, int> dict:
case Dictionary<string, int?> dict:
{
var newKeys = keys.Skip(1).ToArray();

data = dict.ToDictionary(x => x.Key, x => (object)x.Value);
data = dict.ToDictionary(x => x.Key, x => (object?) x.Value);
keys = newKeys;
continue;
}
case Dictionary<string, bool> dict:
case Dictionary<string, bool?> dict:
{
var newKeys = keys.Skip(1).ToArray();

data = dict.ToDictionary(x => x.Key, x => (object)x.Value);
data = dict.ToDictionary(x => x.Key, x => (object?) x.Value);
keys = newKeys;
continue;
}
Expand All @@ -64,7 +64,7 @@ public partial class Helper
[GeneratedRegex(@"(?<name>\{.*?\})")]
private static partial Regex WholeKeyRegex();

public static string ReplaceKeysWithData(string str, Dictionary<string, object> data)
public static string ReplaceKeysWithData(string str, Dictionary<string, object?> data)
{
var matches = WholeKeyRegex().Matches(str).Select(x => x.Groups["name"].Value).ToArray();

Expand Down
2 changes: 1 addition & 1 deletion Htmt/IAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public interface IAttributeParser
{
public string XTag { get; }

public void Parse(XmlDocument xml, Dictionary<string, object> data, XmlNodeList? nodes);
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes);
}
Loading

0 comments on commit b289505

Please sign in to comment.