From 0e4a99332b3b03991cd0c5f90fc36f4dfe62d158 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 18 Jul 2022 21:32:39 +0200 Subject: [PATCH] Fixes #29 by generating valid keys --- I18N Commander/Processor/SectionProcessor.cs | 17 ++----- .../Processor/TextElementProcessor.cs | 15 +----- I18N Commander/Processor/Utils.cs | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 I18N Commander/Processor/Utils.cs diff --git a/I18N Commander/Processor/SectionProcessor.cs b/I18N Commander/Processor/SectionProcessor.cs index 7b291b7..f66275b 100644 --- a/I18N Commander/Processor/SectionProcessor.cs +++ b/I18N Commander/Processor/SectionProcessor.cs @@ -42,21 +42,10 @@ public static class SectionProcessor public static async Task> AddSection(string text, string? parentKey) { await using var db = ProcessorMeta.ServiceProvider.GetRequiredService(); - - // Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed: - var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); - - // Check, if this key already exists: - if (await db.Sections.AnyAsync(n => n.DataKey == key)) - { - var rng = new Random(); - while (await db.Sections.AnyAsync(n => n.DataKey == key)) - { - // Add a random number to the end of the key: - key += $"_{rng.Next(1, 10_000)}"; - } - } + // Generate the key: + var key = await Utils.GenerateCode(text, db.Sections, (n, key) => n.DataKey == key); + // In the case, when the user adds a section to the root, handle the insert differently: if (string.IsNullOrWhiteSpace(parentKey)) { diff --git a/I18N Commander/Processor/TextElementProcessor.cs b/I18N Commander/Processor/TextElementProcessor.cs index be918a9..c8e304b 100644 --- a/I18N Commander/Processor/TextElementProcessor.cs +++ b/I18N Commander/Processor/TextElementProcessor.cs @@ -23,20 +23,9 @@ public static class TextElementProcessor var currentSection = await db.Sections.FirstOrDefaultAsync(n => n.DataKey == currentSectionKey); if (currentSection is null) throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); - - // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: - var code = string.Join('_', elementName.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); - // Check, if this key already exists: - if (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) - { - var rng = new Random(); - while (await db.TextElements.AnyAsync(n => n.Section == currentSection && n.Code == code)) - { - // Add a random number to the end of the key: - code += $"_{rng.Next(1, 10_000)}"; - } - } + // Generate a code: + var code = await Utils.GenerateCode(elementName, db.TextElements, (n, code) => n.Section == currentSection && n.Code == code); var textElement = new TextElement { diff --git a/I18N Commander/Processor/Utils.cs b/I18N Commander/Processor/Utils.cs new file mode 100644 index 0000000..9c19df2 --- /dev/null +++ b/I18N Commander/Processor/Utils.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Processor; + +internal static class Utils +{ + private static readonly Random RNG = new(); + + /// + /// Generates a code out of this name. + /// + /// The name where the code based on + /// The data class + /// The selector to check, if that key already exists. The string parameter is the current code to check. + /// The generated code + internal static async Task GenerateCode(string name, DbSet db, Expression> selector) where TDbSet : class + { + // Filter all non-alphanumeric characters from the name by allowing only A-Z, a-z, 0-9, and spaces from the ASCII table: + name = new string(name.Where(c => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9' or ' ').ToArray()); + + // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: + var code = string.Join('_', name.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); + + // + // The Any() query want's an Expression, but we have an Expression, though. + // Therefore, we have to currying the string away: + var typeDbSet = Expression.Parameter(typeof(TDbSet), null); + var curriedSelector = Expression.Lambda>( + Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), + typeDbSet + ); + + // Check, if this key already exists. If so, add a random number to the end of the key: + if (await db.AnyAsync(curriedSelector)) + while (await db.AnyAsync(curriedSelector)) + { + code += $"_{RNG.Next(1, 10_000)}"; + + // Due to the changed code & since the string is a constant, we have to re-currying the string away: + curriedSelector = Expression.Lambda>( + Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), + typeDbSet + ); + } + + return code; + } +} \ No newline at end of file