Fixes #29 by generating valid keys

This commit is contained in:
Thorsten Sommer 2022-07-18 21:32:39 +02:00
parent d36a258392
commit 0e4a99332b
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
3 changed files with 54 additions and 27 deletions

View File

@ -43,19 +43,8 @@ public static class SectionProcessor
{ {
await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>(); await using var db = ProcessorMeta.ServiceProvider.GetRequiredService<DataContext>();
// Remove any whitespaces from the section name, regardless of how many e.g. spaces the user typed: // Generate the key:
var key = string.Join('_', text.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); var key = await Utils.GenerateCode(text, db.Sections, (n, key) => n.DataKey == key);
// 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)}";
}
}
// In the case, when the user adds a section to the root, handle the insert differently: // In the case, when the user adds a section to the root, handle the insert differently:
if (string.IsNullOrWhiteSpace(parentKey)) if (string.IsNullOrWhiteSpace(parentKey))

View File

@ -24,19 +24,8 @@ public static class TextElementProcessor
if (currentSection is null) if (currentSection is null)
throw new ArgumentOutOfRangeException(nameof(currentSectionKey)); throw new ArgumentOutOfRangeException(nameof(currentSectionKey));
// Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: // Generate a code:
var code = string.Join('_', elementName.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); var code = await Utils.GenerateCode(elementName, db.TextElements, (n, code) => n.Section == currentSection && n.Code == code);
// 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)}";
}
}
var textElement = new TextElement var textElement = new TextElement
{ {

View File

@ -0,0 +1,49 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
namespace Processor;
internal static class Utils
{
private static readonly Random RNG = new();
/// <summary>
/// Generates a code out of this name.
/// </summary>
/// <param name="name">The name where the code based on</param>
/// <param name="db">The data class</param>
/// <param name="selector">The selector to check, if that key already exists. The string parameter is the current code to check.</param>
/// <returns>The generated code</returns>
internal static async Task<string> GenerateCode<TDbSet>(string name, DbSet<TDbSet> db, Expression<Func<TDbSet, string, bool>> 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<T, bool>, but we have an Expression<T, string, bool>, though.
// Therefore, we have to currying the string away:
var typeDbSet = Expression.Parameter(typeof(TDbSet), null);
var curriedSelector = Expression.Lambda<Func<TDbSet, bool>>(
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<Func<TDbSet, bool>>(
Expression.Invoke(selector, typeDbSet, Expression.Constant(code)),
typeDbSet
);
}
return code;
}
}