Skip to content

Commit

Permalink
Merge pull request #18 from askonomm/16-date-formatting-attribute-parser
Browse files Browse the repository at this point in the history
Implement ExpressionModifierParser and a few default modifiers.
  • Loading branch information
askonomm authored Oct 27, 2024
2 parents 1d5eded + 206dd38 commit 77575fd
Show file tree
Hide file tree
Showing 30 changed files with 1,069 additions and 377 deletions.
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

0 comments on commit 77575fd

Please sign in to comment.