From 0814b00c849f309e19a9a6829e95441792a2128c Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 26 May 2020 19:04:08 +0200 Subject: [PATCH] Rule formatter transformations. (#525) --- .../HandleRules/RuleEventFormatter.cs | 105 +++++++++++++++--- .../HandleRules/RuleEventFormatterTests.cs | 48 ++++++++ 2 files changed, 135 insertions(+), 18 deletions(-) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index c8824317e..6b9de787e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -13,6 +13,7 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Scripting; @@ -27,9 +28,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules public class RuleEventFormatter { private const string Fallback = "null"; - private static readonly Regex RegexPatternOld = new Regex(@"^(?[^_]*)_(?[^\s]*)", RegexOptions.Compiled); - private static readonly Regex RegexPatternNew = new Regex(@"^\{(?[^_]*)_(?[^\s]*)\}", RegexOptions.Compiled); - private readonly List<(char[] Pattern, Func Replacer)> patterns = new List<(char[] Pattern, Func Replacer)>(); + private static readonly Regex RegexPatternOld = new Regex(@"^(?(?[^_]*)_(?[^\s]*))", RegexOptions.Compiled); + private static readonly Regex RegexPatternNew = new Regex(@"^\{(?(?[^_]*)_(?[^\s]*))([\s]*\|[\s]*(?.*)){0,1}\}", RegexOptions.Compiled); + private readonly List<(string Pattern, Func Replacer)> patterns = new List<(string Pattern, Func Replacer)>(); private readonly IJsonSerializer jsonSerializer; private readonly IUrlGenerator urlGenerator; private readonly IScriptEngine scriptEngine; @@ -63,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules private void AddPattern(string placeholder, Func generator) { - patterns.Add((placeholder.ToCharArray(), generator)); + patterns.Add((placeholder, generator)); } public virtual string ToPayload(T @event) @@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules { parts.Add((currentOffset, i - currentOffset, default)); - var (replacement, length) = GetReplacement(span.Slice(i + 1), @event); + var (replacement, length) = GetReplacement(span.Slice(i + 1).ToString(), @event); if (length > 0) { @@ -139,35 +140,61 @@ namespace Squidex.Domain.Apps.Core.HandleRules return sb.ToString(); } - private (string Result, int Length) GetReplacement(ReadOnlySpan test, EnrichedEvent @event) + private (string Result, int Length) GetReplacement(string test, EnrichedEvent @event) { - for (var j = 0; j < patterns.Count; j++) + var (isNewRegex, match) = Match(test); + + if (match.Success) { - var (pattern, replacer) = patterns[j]; + var (length, text) = ResolveOldPatterns(match, isNewRegex, @event); - if (test.StartsWith(pattern, StringComparison.OrdinalIgnoreCase)) + if (length == 0) { - return (replacer(@event) ?? Fallback, pattern.Length); + (length, text) = ResolveFromPath(match, @event); } + + return (TransformText(text, match.Groups["Transform"]?.Value), length); } - var currentString = test.ToString(); + return (Fallback, 0); + } - var match = RegexPatternNew.Match(currentString); + private (bool IsNew, Match) Match(string test) + { + var match = RegexPatternNew.Match(test); - if (!match.Success) + if (match.Success) { - match = RegexPatternOld.Match(currentString); + return (true, match); } - if (match.Success) + return (false, RegexPatternOld.Match(test)); + } + + private (int Length, string? Result) ResolveOldPatterns(Match match, bool isNewRegex, EnrichedEvent @event) + { + var fullPath = match.Groups["FullPath"].Value; + + for (var j = 0; j < patterns.Count; j++) { - var path = match.Groups["Path"].Value.Split('.', StringSplitOptions.RemoveEmptyEntries); + var (pattern, replacer) = patterns[j]; + + if (fullPath.StartsWith(pattern, StringComparison.OrdinalIgnoreCase)) + { + var result = replacer(@event); - return (CalculateData(@event, path) ?? Fallback, match.Length); + if (isNewRegex) + { + return (match.Length, result); + } + else + { + return (pattern.Length, result); + } + } } - return (Fallback, 0); + return default; } private static string TimestampDate(EnrichedEvent @event) @@ -300,6 +327,48 @@ namespace Squidex.Domain.Apps.Core.HandleRules return null; } + private static string TransformText(string? text, string? transform) + { + if (text != null && !string.IsNullOrWhiteSpace(transform)) + { + var transformations = transform.Split("|", StringSplitOptions.RemoveEmptyEntries); + + foreach (var transformation in transformations) + { + switch (transformation.Trim().ToLowerInvariant()) + { + case "lower": + text = text.ToLowerInvariant(); + break; + case "upper": + text = text.ToUpperInvariant(); + break; + case "escape": + text = JsonConvert.ToString(text); + text = text[1..^1]; + break; + case "slugify": + text = text.Slugify(); + break; + case "trim": + text = text.Trim(); + break; + } + } + } + + return text ?? Fallback; + } + + private (int Length, string? Result) ResolveFromPath(Match match, EnrichedEvent @event) + { + var path = match.Groups["Path"].Value.Split('.', StringSplitOptions.RemoveEmptyEntries); + + var result = CalculateData(@event, path); + + return (match.Length, result); + } + private static string? CalculateData(object @event, string[] path) { object? current = @event; diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs index 3021e7dc3..0c478d6df 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -528,5 +528,53 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules Assert.Equal("Created", result); } + + [Theory] + [InlineData("Found in ${ASSET_FILENAME | Upper}.docx", "Found in DONALD DUCK.docx")] + [InlineData("Found in ${ASSET_FILENAME| Upper }.docx", "Found in DONALD DUCK.docx")] + [InlineData("Found in ${ASSET_FILENAME|Upper }.docx", "Found in DONALD DUCK.docx")] + public void Should_transform_replacements_and_igore_whitepsaces(string script, string expect) + { + var @event = new EnrichedAssetEvent { FileName = "Donald Duck" }; + + var result = sut.Format(script, @event); + + Assert.Equal(expect, result); + } + + [Theory] + [InlineData("Found in ${ASSET_FILENAME | Escape | Upper}.docx", "Found in DONALD\\\"DUCK .docx")] + [InlineData("Found in ${ASSET_FILENAME | Escape}.docx", "Found in Donald\\\"Duck .docx")] + [InlineData("Found in ${ASSET_FILENAME | Upper}.docx", "Found in DONALD\"DUCK .docx")] + [InlineData("Found in ${ASSET_FILENAME | Lower}.docx", "Found in donald\"duck .docx")] + [InlineData("Found in ${ASSET_FILENAME | Slugify}.docx", "Found in donald-duck.docx")] + [InlineData("Found in ${ASSET_FILENAME | Trim}.docx", "Found in Donald\"Duck.docx")] + public void Should_transform_replacements(string script, string expect) + { + var @event = new EnrichedAssetEvent { FileName = "Donald\"Duck " }; + + var result = sut.Format(script, @event); + + Assert.Equal(expect, result); + } + + [Theory] + [InlineData("From ${USER_NAME | Escape | Upper}", "From DONALD\\\"DUCK ")] + [InlineData("From ${USER_NAME | Escape}", "From Donald\\\"Duck ")] + [InlineData("From ${USER_NAME | Upper}", "From DONALD\"DUCK ")] + [InlineData("From ${USER_NAME | Lower}", "From donald\"duck ")] + [InlineData("From ${USER_NAME | Slugify}", "From donald-duck")] + [InlineData("From ${USER_NAME | Trim}", "From Donald\"Duck")] + public void Should_transform_replacements_with_simple_pattern(string script, string expect) + { + var @event = new EnrichedContentEvent { User = user }; + + A.CallTo(() => user.Claims) + .Returns(new List { new Claim(SquidexClaimTypes.DisplayName, "Donald\"Duck ") }); + + var result = sut.Format(script, @event); + + Assert.Equal(expect, result); + } } }