Browse Source

Liquid support for new text helpers.

pull/544/head
Sebastian 6 years ago
parent
commit
f77ed4de1c
  1. 79
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs
  2. 34
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs
  3. 36
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs
  4. 31
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringWordsFluidExtension.cs
  5. 137
      backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs
  6. 7
      backend/src/Squidex/Config/Domain/InfrastructureServices.cs
  7. 1
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs
  8. 64
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs

79
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs

@ -6,12 +6,8 @@
// ==========================================================================
using System;
using System.IO;
using System.Text;
using HtmlAgilityPack;
using Jint;
using Jint.Native;
using Markdig;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting.Extensions
@ -60,13 +56,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{
try
{
var document = LoadHtml(text);
var sb = new StringBuilder();
WriteTextTo(document.DocumentNode, sb);
return sb.ToString().Trim(' ', '\n', '\r');
return TextHelpers.Html2Text(text);
}
catch
{
@ -74,76 +64,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
}
};
private static HtmlDocument LoadHtml(string text)
{
var document = new HtmlDocument();
document.LoadHtml(text);
return document;
}
private static void WriteTextTo(HtmlNode node, StringBuilder sb)
{
switch (node.NodeType)
{
case HtmlNodeType.Comment:
break;
case HtmlNodeType.Document:
WriteChildrenTextTo(node, sb);
break;
case HtmlNodeType.Text:
var html = ((HtmlTextNode)node).Text;
if (HtmlNode.IsOverlappedClosingElement(html))
{
break;
}
if (!string.IsNullOrWhiteSpace(html))
{
sb.Append(HtmlEntity.DeEntitize(html));
}
break;
case HtmlNodeType.Element:
switch (node.Name)
{
case "p":
sb.AppendLine();
break;
case "br":
sb.AppendLine();
break;
case "style":
return;
case "script":
return;
}
if (node.HasChildNodes)
{
WriteChildrenTextTo(node, sb);
}
break;
}
}
private static void WriteChildrenTextTo(HtmlNode node, StringBuilder sb)
{
foreach (var child in node.ChildNodes)
{
WriteTextTo(child, sb);
}
}
private readonly Func<string, JsValue> markdown2Text = text =>
{
try
{
return Markdown.ToPlainText(text).Trim(' ', '\n', '\r');
return TextHelpers.Markdown2Text(text);
}
catch
{

34
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs

@ -17,27 +17,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{
try
{
var numWords = 0;
for (int i = 1; i < text.Length; i++)
{
if (char.IsWhiteSpace(text[i - 1]))
{
var character = text[i];
if (char.IsLetterOrDigit(character) || char.IsPunctuation(character))
{
numWords++;
}
}
}
if (text.Length > 2)
{
numWords++;
}
return numWords;
return TextHelpers.WordCount(text);
}
catch
{
@ -49,17 +29,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{
try
{
var characterCount = 0;
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]))
{
characterCount++;
}
}
return characterCount;
return TextHelpers.CharacterCount(text);
}
catch
{

36
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs

@ -14,14 +14,7 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
{
public sealed class StringFluidExtension : IFluidExtension
{
public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy)
{
TemplateContext.GlobalFilters.AddFilter("escape", Escape);
TemplateContext.GlobalFilters.AddFilter("slugify", Slugify);
TemplateContext.GlobalFilters.AddFilter("trim", Trim);
}
public static FluidValue Slugify(FluidValue input, FilterArguments arguments, TemplateContext context)
private static readonly FilterDelegate Slugify = (input, arguments, context) =>
{
if (input is StringValue value)
{
@ -31,9 +24,9 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
}
return input;
}
};
public static FluidValue Escape(FluidValue input, FilterArguments arguments, TemplateContext context)
private static readonly FilterDelegate Escape = (input, arguments, context) =>
{
var result = input.ToStringValue();
@ -41,11 +34,30 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
result = result[1..^1];
return FluidValue.Create(result);
}
};
public static FluidValue Trim(FluidValue input, FilterArguments arguments, TemplateContext context)
private static readonly FilterDelegate Markdown2Text = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.Markdown2Text(input.ToStringValue()));
};
private static readonly FilterDelegate Html2Text = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.Html2Text(input.ToStringValue()));
};
private static readonly FilterDelegate Trim = (input, arguments, context) =>
{
return FluidValue.Create(input.ToStringValue().Trim());
};
public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy)
{
TemplateContext.GlobalFilters.AddFilter("html2text", Html2Text);
TemplateContext.GlobalFilters.AddFilter("markdown2text", Markdown2Text);
TemplateContext.GlobalFilters.AddFilter("escape", Escape);
TemplateContext.GlobalFilters.AddFilter("slugify", Slugify);
TemplateContext.GlobalFilters.AddFilter("trim", Trim);
}
}
}

31
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringWordsFluidExtension.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Fluid;
using Fluid.Values;
namespace Squidex.Domain.Apps.Core.Templates.Extensions
{
public class StringWordsFluidExtension : IFluidExtension
{
private static readonly FilterDelegate WordCount = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.WordCount(input.ToStringValue()));
};
private static readonly FilterDelegate CharacterCount = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.CharacterCount(input.ToStringValue()));
};
public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy)
{
TemplateContext.GlobalFilters.AddFilter("word_count", WordCount);
TemplateContext.GlobalFilters.AddFilter("character_count", CharacterCount);
}
}
}

137
backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs

@ -0,0 +1,137 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text;
using HtmlAgilityPack;
using Markdig;
namespace Squidex.Domain.Apps.Core
{
public static class TextHelpers
{
public static string Markdown2Text(string markdown)
{
return Markdown.ToPlainText(markdown).Trim(' ', '\n', '\r');
}
public static string Html2Text(string html)
{
var document = LoadHtml(html);
var sb = new StringBuilder();
WriteTextTo(document.DocumentNode, sb);
return sb.ToString().Trim(' ', '\n', '\r');
}
private static HtmlDocument LoadHtml(string text)
{
var document = new HtmlDocument();
document.LoadHtml(text);
return document;
}
private static void WriteTextTo(HtmlNode node, StringBuilder sb)
{
switch (node.NodeType)
{
case HtmlNodeType.Comment:
break;
case HtmlNodeType.Document:
WriteChildrenTextTo(node, sb);
break;
case HtmlNodeType.Text:
var html = ((HtmlTextNode)node).Text;
if (HtmlNode.IsOverlappedClosingElement(html))
{
break;
}
if (!string.IsNullOrWhiteSpace(html))
{
sb.Append(HtmlEntity.DeEntitize(html));
}
break;
case HtmlNodeType.Element:
switch (node.Name)
{
case "p":
sb.AppendLine();
break;
case "br":
sb.AppendLine();
break;
case "style":
return;
case "script":
return;
}
if (node.HasChildNodes)
{
WriteChildrenTextTo(node, sb);
}
break;
}
}
private static void WriteChildrenTextTo(HtmlNode node, StringBuilder sb)
{
foreach (var child in node.ChildNodes)
{
WriteTextTo(child, sb);
}
}
public static int CharacterCount(string text)
{
var characterCount = 0;
for (int i = 0; i < text.Length; i++)
{
if (char.IsLetter(text[i]))
{
characterCount++;
}
}
return characterCount;
}
public static int WordCount(string text)
{
var numWords = 0;
for (int i = 1; i < text.Length; i++)
{
if (char.IsWhiteSpace(text[i - 1]))
{
var character = text[i];
if (char.IsLetterOrDigit(character) || char.IsPunctuation(character))
{
numWords++;
}
}
}
if (text.Length > 2)
{
numWords++;
}
return numWords;
}
}
}

7
backend/src/Squidex/Config/Domain/InfrastructureServices.cs

@ -86,9 +86,16 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<DateTimeFluidExtension>()
.As<IFluidExtension>();
services.AddSingletonAs<StringFluidExtension>()
.As<IFluidExtension>();
services.AddSingletonAs<StringWordsFluidExtension>()
.As<IFluidExtension>();
services.AddSingletonAs<UserFluidExtension>()
.As<IFluidExtension>();
services.AddSingleton<Func<IIncomingGrainCallContext, string>>(DomainObjectGrainFormatter.Format);
}

1
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs

@ -97,6 +97,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new DateTimeFluidExtension(),
new EventFluidExtensions(urlGenerator),
new StringFluidExtension(),
new StringWordsFluidExtension(),
new UserFluidExtension()
};

64
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Templates/FluidTemplateEngineTests.cs

@ -24,7 +24,9 @@ namespace Squidex.Domain.Apps.Core.Operations.Templates
{
var extensions = new IFluidExtension[]
{
new DateTimeFluidExtension()
new DateTimeFluidExtension(),
new StringFluidExtension(),
new StringWordsFluidExtension()
};
sut = new FluidTemplateEngine(extensions);
@ -113,6 +115,66 @@ namespace Squidex.Domain.Apps.Core.Operations.Templates
Assert.Equal("Hello", result);
}
[Fact]
public async Task Should_format_html_to_text()
{
var template = "{{ e.text | html2text }}";
var value = new
{
Text = "<script>Invalid</script><STYLE>Invalid</STYLE><p>Hello World</p>"
};
var result = await RenderAync(template, value);
Assert.Equal("Hello World", result);
}
[Fact]
public async Task Should_convert_markdown_to_text()
{
var template = "{{ e.text | markdown2text }}";
var value = new
{
Text = "## Hello World"
};
var result = await RenderAync(template, value);
Assert.Equal("Hello World", result);
}
[Fact]
public async Task Should_format_word_count()
{
var template = "{{ e.text | word_count }}";
var value = new
{
Text = "Hello World"
};
var result = await RenderAync(template, value);
Assert.Equal("2", result);
}
[Fact]
public async Task Should_format_character_count()
{
var template = "{{ e.text | character_count }}";
var value = new
{
text = "Hello World"
};
var result = await RenderAync(template, value);
Assert.Equal("10", result);
}
[Fact]
public async Task Should_throw_exception_when_template_invalid()
{

Loading…
Cancel
Save