From 43312c336245ac9f3f549aa332b7155148198bd4 Mon Sep 17 00:00:00 2001 From: Tom Pipinic Date: Tue, 7 Feb 2023 10:54:27 +0100 Subject: [PATCH] AsUrlFriendly extension added --- src/Connected.Services/ServicesExtensions.cs | 10 ++ src/Connected.Services/UrlGenerator.cs | 112 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/Connected.Services/UrlGenerator.cs diff --git a/src/Connected.Services/ServicesExtensions.cs b/src/Connected.Services/ServicesExtensions.cs index a84f6b9..4941bca 100644 --- a/src/Connected.Services/ServicesExtensions.cs +++ b/src/Connected.Services/ServicesExtensions.cs @@ -51,4 +51,14 @@ public static class ServicesExtensions return (f is not null && type.GetInterface(f) is not null) || (nf is not null && type.GetInterface(nf) is not null); } + + public static string AsUrlFriendly(this string value) + { + return UrlGenerator.Generate(value); + } + + public static string AsUrlFriendly(this string value, IEnumerable existing) + { + return UrlGenerator.Generate(value, existing); + } } \ No newline at end of file diff --git a/src/Connected.Services/UrlGenerator.cs b/src/Connected.Services/UrlGenerator.cs new file mode 100644 index 0000000..ce6a527 --- /dev/null +++ b/src/Connected.Services/UrlGenerator.cs @@ -0,0 +1,112 @@ +using System.Text; + +namespace Connected.Services; +internal static class UrlGenerator +{ + private const string ValidCharacters = "abcdefghijklmnopqrstuvzxyw-"; + private const string MinusReplacements = "_ .\t\r\n"; + static UrlGenerator() + { + Replacements = new(StringComparer.OrdinalIgnoreCase) + { + {"č", "c"}, + {"š", "s"}, + {"ž", "z"}, + {"đ", "d"}, + {"ć", "c"} + }; + } + + private static Dictionary Replacements { get; } + + public static string Generate(string text) + { + if (string.IsNullOrEmpty(text)) + throw new NullReferenceException(nameof(text)); + + var result = new StringBuilder(); + /* + * Analyze each character. + */ + foreach (var s in text.Trim()) + { + /* + * If it's in a ValidCharacters collection that's fine, we just copy it + * into the result. + */ + if (ValidCharacters.Contains(s, StringComparison.OrdinalIgnoreCase)) + { + result.Append(s); + continue; + } + /* + * Look if it's in the MinusReplacements collection which means we'll + * replace the character with the minus sign. + */ + if (MinusReplacements.Contains(s, StringComparison.OrdinalIgnoreCase)) + { + result.Append('-'); + continue; + } + /* + * Last chance for the character to be included in the result is to + * resolve the hardcoded character replacements. + */ + if (Replacements.TryGetValue(s.ToString(), out var replacement)) + result.Append(replacement); + } + /* + * It's possible that no characters are included in the result. If that's + * the case we are going to include an a character just for the sake of + * being a valid string. + */ + if (result.Length == 0) + result.Append('a'); + /* + * Remove any redundant minus characters and we are done. + */ + var sb = new StringBuilder(); + var active = false; + + for (var i = 0; i < result.Length; i++) + { + if (result[i] == '-') + { + if (active) + continue; + + active = true; + sb.Append(result[i]); + } + else + { + active = false; + sb.Append(result[i]); + } + } + /* + * Remove trailing minus if exists. + */ + return sb.ToString().Trim().Trim('-').ToLowerInvariant(); + } + + public static string Generate(string text, IEnumerable existing) + { + var prepared = Generate(text); + + if (!existing.Any()) + return prepared; + + var idx = 0; + + while (true) + { + var key = $"{prepared}-{idx}"; + + if (!existing.Contains(key, StringComparer.OrdinalIgnoreCase)) + return key; + + idx++; + } + } +} \ No newline at end of file