From 93607b03ae015d90a5b0f39bb48c3e98aed4e837 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 6 Jul 2020 14:26:58 +0200 Subject: [PATCH] Scripting extensions. --- .../Extensions/StringJintExtension.cs | 105 ++++++++++++++++++ .../Extensions/StringWordsJintExtension.cs | 77 +++++++++++++ ...Squidex.Domain.Apps.Core.Operations.csproj | 2 + .../Config/Domain/InfrastructureServices.cs | 3 + .../RuleEventFormatterCompareTests.cs | 3 +- .../HandleRules/RuleEventFormatterTests.cs | 3 +- .../Scripting/JintScriptEngineHelperTests.cs | 71 +++++++++++- .../Scripting/JintScriptEngineTests.cs | 3 +- 8 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs index 4a7569bbc..79e1a067d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs @@ -6,8 +6,12 @@ // ========================================================================== 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 @@ -52,12 +56,113 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions } }; + private readonly Func html2Text = text => + { + try + { + var document = LoadHtml(text); + + var sb = new StringBuilder(); + + WriteTextTo(document.DocumentNode, sb); + + return sb.ToString().Trim(' ', '\n', '\r'); + } + catch + { + return JsValue.Undefined; + } + }; + + 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 markdown2Text = text => + { + try + { + return Markdown.ToPlainText(text).Trim(' ', '\n', '\r'); + } + catch + { + return JsValue.Undefined; + } + }; + + public Func Html2Text => html2Text; + public void Extend(Engine engine) { engine.SetValue("slugify", slugify); engine.SetValue("toCamelCase", toCamelCase); engine.SetValue("toPascalCase", toPascalCase); + + engine.SetValue("html2Text", Html2Text); + + engine.SetValue("markdown2Text", markdown2Text); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs new file mode 100644 index 000000000..2de45b6eb --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringWordsJintExtension.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Jint; +using Jint.Native; + +namespace Squidex.Domain.Apps.Core.Scripting.Extensions +{ + public sealed class StringWordsJintExtension : IJintExtension + { + private readonly Func wordCount = text => + { + 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; + } + catch + { + return JsValue.Undefined; + } + }; + + private readonly Func characterCount = text => + { + try + { + var characterCount = 0; + + for (int i = 0; i < text.Length; i++) + { + if (char.IsLetter(text[i])) + { + characterCount++; + } + } + + return characterCount; + } + catch + { + return JsValue.Undefined; + } + }; + + public void Extend(Engine engine) + { + engine.SetValue("wordCount", wordCount); + + engine.SetValue("characterCount", characterCount); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index f51c23b51..256f8929f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -17,7 +17,9 @@ + + diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index 4d2c40d75..62702c299 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -71,6 +71,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs() .As(); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs index e6e24824e..a1dfae6b5 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterCompareTests.cs @@ -109,7 +109,8 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { new DateTimeJintExtension(), new EventJintExtension(urlGenerator), - new StringJintExtension() + new StringJintExtension(), + new StringWordsJintExtension() }; var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); 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 f57f7d6fb..7ceb15c23 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 @@ -103,7 +103,8 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { new DateTimeJintExtension(), new EventJintExtension(urlGenerator), - new StringJintExtension() + new StringJintExtension(), + new StringWordsJintExtension() }; var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs index b31090f98..7678bcbf6 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs @@ -33,7 +33,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { new DateTimeJintExtension(), new HttpJintExtension(httpClientFactory), - new StringJintExtension() + new StringJintExtension(), + new StringWordsJintExtension() }; var cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); @@ -44,6 +45,74 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }; } + [Fact] + public void Should_convert_html_to_text() + { + const string script = @" + return html2Text(value); + "; + + var vars = new ScriptVars + { + ["value"] = "

Hello World

" + }; + + var result = sut.Execute(vars, script).ToString(); + + Assert.Equal("Hello World", result); + } + + [Fact] + public void Should_convert_markdown_to_text() + { + const string script = @" + return markdown2Text(value); + "; + + var vars = new ScriptVars + { + ["value"] = "## Hello World" + }; + + var result = sut.Execute(vars, script).ToString(); + + Assert.Equal("Hello World", result); + } + + [Fact] + public void Should_count_words() + { + const string script = @" + return wordCount(value); + "; + + var vars = new ScriptVars + { + ["value"] = "Hello, World" + }; + + var result = ((JsonNumber)sut.Execute(vars, script)).Value; + + Assert.Equal(2, result); + } + + [Fact] + public void Should_count_characters() + { + const string script = @" + return characterCount(value); + "; + + var vars = new ScriptVars + { + ["value"] = "Hello, World" + }; + + var result = ((JsonNumber)sut.Execute(vars, script)).Value; + + Assert.Equal(10, result); + } + [Fact] public void Should_camel_case_value() { diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs index 86ca2925f..25267a819 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs @@ -40,7 +40,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { new DateTimeJintExtension(), new HttpJintExtension(httpClientFactory), - new StringJintExtension() + new StringJintExtension(), + new StringWordsJintExtension() }; var httpResponse = new HttpResponseMessage(HttpStatusCode.OK)