Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ExpressionModifierParser and a few default modifiers. #18

Merged
merged 7 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/\
/.idea/
/.idea/
Htmt.sln.DotSettings.user
81 changes: 81 additions & 0 deletions Htmt/AttributeParsers/BaseAttributeParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Xml;

namespace Htmt.AttributeParsers;

/// <summary>
/// Base class for attribute parsers that provides some common functionality.
/// </summary>
public partial class BaseAttributeParser : IAttributeParser
{
/// <summary>
/// XML Tag selector for finding the relevant nodes.
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public virtual string XTag => throw new NotImplementedException();

/// <summary>
/// The entire XML document that is being parsed.
/// </summary>
public XmlDocument Xml { get; set; } = new();

/// <summary>
/// Templating data.
/// </summary>
public Dictionary<string, object?> Data { get; set; } = new();

/// <summary>
/// List of expression modifiers.
/// </summary>
public IExpressionModifier[] ExpressionModifiers { get; set; } = [];

[GeneratedRegex(@"(?<name>\{.*?\})")]
private static partial Regex WholeKeyRegex();

/// <summary>
/// Parser the expression where it replaces variables with their data, and applies
/// expression modifiers.
/// </summary>
/// <param name="str"></param>
/// <returns>Returns the parsed expression as a string.</returns>
protected string ParseExpression(string str)
{
var matches = WholeKeyRegex().Matches(str).Select(x => x.Groups["name"].Value).ToArray();

foreach (var match in matches)
{
var strippedName = match[1..^1];
var expressionModifierParser = new ExpressionModifierParser { Data = Data, ExpressionModifiers = ExpressionModifiers };
var value = expressionModifierParser.Parse(strippedName);

if (value != null)
{
str = value switch
{
string s => str.Replace(match, s),
int i => str.Replace(match, i.ToString()),
double d => str.Replace(match, d.ToString(CultureInfo.CurrentCulture)),
bool b => str.Replace(match, b.ToString()),
_ => str.Replace(match, value.ToString()),
};
}
else
{
str = str.Replace(match, "");
}
}

return str;
}

/// <summary>
/// A method that is called to parse the XML nodes.
/// </summary>
/// <param name="nodes"></param>
/// <exception cref="NotImplementedException"></exception>
public virtual void Parse(XmlNodeList? nodes)
{
throw new NotImplementedException();
}
}
23 changes: 13 additions & 10 deletions Htmt/AttributeParsers/ForAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

namespace Htmt.AttributeParsers;

public class ForAttributeParser : IAttributeParser
/// <summary>
/// A parser for the x:for attribute.
/// </summary>
public class ForAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:for]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:for]";

public override void Parse(XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
{
return;
return;
}

Parallel.ForEach(nodes.Cast<XmlNode>(), node =>
Expand All @@ -20,18 +23,18 @@ public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList

var collection = n.GetAttribute("x:for");
var asVar = n.GetAttribute("x:as");

n.RemoveAttribute("x:for");
n.RemoveAttribute("x:as");

var value = Helper.FindValueByKeys(data, collection.Split('.'));
var value = Utils.FindValueByKeys(Data, collection.Split('.'));
if (value is not IEnumerable<object> enumerable) return;

var fragment = xml.CreateDocumentFragment();
var fragment = Xml.CreateDocumentFragment();

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

if (!string.IsNullOrEmpty(asVar))
{
Expand All @@ -40,7 +43,7 @@ public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList

var iterationParser = new Parser { Template = n.OuterXml, Data = iterationData };
var itemXml = iterationParser.ToXml();
var importedNode = xml.ImportNode(itemXml, true);
var importedNode = Xml.ImportNode(itemXml, true);

fragment.AppendChild(importedNode);
}
Expand Down
19 changes: 11 additions & 8 deletions Htmt/AttributeParsers/GenericValueAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@

namespace Htmt.AttributeParsers;

public class GenericValueAttributeParser : IAttributeParser
/// <summary>
/// A parser for generic value attributes.
/// </summary>
public class GenericValueAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@*[starts-with(name(), 'x:')]]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@*[starts-with(name(), 'x:')]]";

public override void Parse(XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
{
return;
}

foreach (var node in nodes)
{
if (node is not XmlElement n) continue;

var attributes = n.Attributes.Cast<XmlAttribute>()
.Where(a => a.Name.StartsWith("x:"))
.ToList();

foreach (var attr in attributes)
{
var val = n.GetAttribute(attr.Name);
var newVal = Helper.ReplaceKeysWithData(val, data);
var newVal = ParseExpression(val);
n.SetAttribute(attr.Name[2..], newVal);
n.RemoveAttribute(attr.Name);
}
Expand Down
21 changes: 12 additions & 9 deletions Htmt/AttributeParsers/IfAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

namespace Htmt.AttributeParsers;

public class IfAttributeParser : IAttributeParser
/// <summary>
/// A parser for the x:if attribute.
/// </summary>
public class IfAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:if]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:if]";

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

var key = n.GetAttribute("x:if");
n.RemoveAttribute("x:if");

// if key is a single word, we just check for a truthy value
if (!key.Contains(' '))
{
var value = Helper.FindValueByKeys(data, key.Split('.'));
var value = Utils.FindValueByKeys(Data, key.Split('.'));

// Remove node if value is null
if (value == null)
Expand All @@ -51,12 +54,12 @@ public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList
n.ParentNode?.RemoveChild(n);
}
}

// if key contains multiple words, evaluate the expression with ExpressionValidator
else
{
var expression = new ExpressionValidator(key);
var result = expression.Validates(data);
var validator = new ExpressionBooleanValidator { Expression = key, Data = Data };
var result = validator.Validates();

if (!result)
{
Expand Down
17 changes: 10 additions & 7 deletions Htmt/AttributeParsers/InnerHtmlAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

namespace Htmt.AttributeParsers;

public class InnerHtmlAttributeParser: IAttributeParser
/// <summary>
/// A parser for the x:inner-html attribute.
/// </summary>
public class InnerHtmlAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:inner-html]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:inner-html]";

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

var innerHtmlVal = n.GetAttribute("x:inner-html");
n.RemoveAttribute("x:inner-html");

if (string.IsNullOrEmpty(innerHtmlVal)) continue;

var innerXml = new XmlDocument();
innerXml.LoadXml($"<root>{Helper.ReplaceKeysWithData(innerHtmlVal, data)}</root>");
innerXml.LoadXml($"<root>{ParseExpression(innerHtmlVal)}</root>");
n.InnerXml = innerXml.DocumentElement?.InnerXml ?? string.Empty;
}
}
Expand Down
19 changes: 11 additions & 8 deletions Htmt/AttributeParsers/InnerPartialAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@

namespace Htmt.AttributeParsers;

public class InnerPartialAttributeParser : IAttributeParser
/// <summary>
/// A parser for the x:inner-partial attribute.
/// </summary>
public class InnerPartialAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:inner-partial]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:inner-partial]";

public override void Parse(XmlNodeList? nodes)
{
// No nodes found
if (nodes == null || nodes.Count == 0)
{
return;
}

foreach (var node in nodes)
{
if (node is not XmlElement n) continue;

var innerPartial = n.GetAttribute("x:inner-partial");
n.RemoveAttribute("x:inner-partial");

if (string.IsNullOrEmpty(innerPartial)) continue;

if (Helper.FindValueByKeys(data, innerPartial.Split('.')) is not string partial) continue;
if (Utils.FindValueByKeys(Data, innerPartial.Split('.')) is not string partial) continue;

n.InnerXml = new Parser { Data = data, Template = partial }.ToXml().OuterXml;
n.InnerXml = new Parser { Data = Data, Template = partial }.ToXml().OuterXml;
}
}
}
14 changes: 8 additions & 6 deletions Htmt/AttributeParsers/InnerTextAttributeParser.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.Text.RegularExpressions;
using System.Xml;

namespace Htmt.AttributeParsers;

public class InnerTextAttributeParser : IAttributeParser
/// <summary>
/// A parser for the x:inner-text attribute.
/// </summary>
public class InnerTextAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:inner-text]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:inner-text]";

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

if (string.IsNullOrEmpty(innerVal)) continue;

n.InnerText = Helper.ReplaceKeysWithData(innerVal, data);
n.InnerText = ParseExpression(innerVal);
}
}
}
24 changes: 14 additions & 10 deletions Htmt/AttributeParsers/OuterHtmlAttributeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

namespace Htmt.AttributeParsers;

public class OuterHtmlAttributeParser : IAttributeParser
/// <summary>
/// A parser for the x:outer-html attribute.
/// </summary>
public class OuterHtmlAttributeParser : BaseAttributeParser
{
public string XTag => "//*[@x:outer-html]";
public void Parse(XmlDocument xml, Dictionary<string, object?> data, XmlNodeList? nodes)
public override string XTag => "//*[@x:outer-html]";

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

var outerHtmlVal = n.GetAttribute("x:outer-html");
n.RemoveAttribute("x:outer-html");

if (string.IsNullOrEmpty(outerHtmlVal)) continue;

var outerXml = new XmlDocument();
outerXml.LoadXml($"<root>{Helper.ReplaceKeysWithData(outerHtmlVal, data)}</root>");

outerXml.LoadXml($"<root>{ParseExpression(outerHtmlVal)}</root>");


if (outerXml.DocumentElement?.FirstChild == null) continue;
var importedNode = xml.ImportNode(outerXml.DocumentElement.FirstChild, true);

var importedNode = Xml.ImportNode(outerXml.DocumentElement.FirstChild, true);
n.ParentNode?.ReplaceChild(importedNode, n);
}
}
Expand Down
Loading