using System.Text; namespace PointlessWaymarks.CommonTools; public static class SlugTools { private static string? ConvertEdgeCases(char c, bool toLower) { var swap = c switch { 'ı' => "i", 'ł' => "l", 'Ł' => toLower ? "l" : "L", 'đ' => "d", 'ß' => "ss", 'ø' => "o", 'Þ' => "th", _ => null }; return swap; } /// /// This is intended for use in the live processing of user input where you want to create slug like strings but to be /// friendly to typed input (for example so trailing spaces must be allowed to avoid fighting the user) - in general /// this is not as strict as CreateSpacedString. /// /// /// /// /// public static string CreateRelaxedInputSpacedString(bool toLower, string? value, List? allowedBeyondAtoZ1To9 = null) { if (value == null) return ""; allowedBeyondAtoZ1To9 ??= []; var normalized = value.Normalize(NormalizationForm.FormKD); var len = normalized.Length; var sb = new StringBuilder(len); for (var i = 0; i < len; i++) { var c = normalized[i]; if (c is >= 'a' and <= 'z' or >= '0' and <= '9' || allowedBeyondAtoZ1To9.Contains(c)) { sb.Append(c); } else if (c is >= 'A' and <= 'Z') { // Tricky way to convert to lowercase if (toLower) sb.Append((char)(c | 32)); else sb.Append(c); } else { var swap = ConvertEdgeCases(c, toLower); if (swap != null) sb.Append(swap); } } return sb.ToString(); } /// /// Creates a Slug. /// /// /// /// public static string CreateSlug(bool toLower, params string[] values) { return CreateSlug(toLower, string.Join("-", values)); } /// /// Creates a slug. /// /// /// /// /// // References: // https://stackoverflow.com/questions/25259/how-does-stack-overflow-generate-its-seo-friendly-urls // http://www.unicode.org/reports/tr15/tr15-34.html // https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696 // https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486 // https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string public static string CreateSlug(bool toLower, string? value, int maxLength = 100) { if (value == null) return ""; var normalized = value.Normalize(NormalizationForm.FormKD); var len = normalized.Length; var prevDash = false; var sb = new StringBuilder(len); for (var i = 0; i < len; i++) { var c = normalized[i]; if (c is >= 'a' and <= 'z' or >= '0' and <= '9') { if (prevDash) { sb.Append('-'); prevDash = false; } sb.Append(c); } else if (c is >= 'A' and <= 'Z') { if (prevDash) { sb.Append('-'); prevDash = false; } // Tricky way to convert to lowercase if (toLower) sb.Append((char)(c | 32)); else sb.Append(c); } else if (c is ' ' or ',' or '.' or '/' or '\\' or '-' or '_' or '=') { if (!prevDash && sb.Length > 0) prevDash = true; } else { var swap = ConvertEdgeCases(c, toLower); if (swap != null) { if (prevDash) { sb.Append('-'); prevDash = false; } sb.Append(swap); } } if (sb.Length == maxLength) break; } return sb.ToString(); } /// /// This method mimics the Create method but is focused on creating a spaced string with the intent that in some cases /// this may create a format that communicates the same information and intent as the Create Slug method but is easier /// to read and more user friendly. /// /// /// /// /// public static string CreateSpacedString(bool toLower, string value, int? maxLength = 100) { var normalized = value.Normalize(NormalizationForm.FormKD); var len = normalized.Length; var previousSpace = false; var sb = new StringBuilder(len); for (var i = 0; i < len; i++) { var c = normalized[i]; if (c is >= 'a' and <= 'z' or >= '0' and <= '9' or '_' or '-') { if (previousSpace) { sb.Append(' '); previousSpace = false; } sb.Append(c); } else if (c is >= 'A' and <= 'Z') { if (previousSpace) { sb.Append(' '); previousSpace = false; } // Tricky way to convert to lowercase if (toLower) sb.Append((char)(c | 32)); else sb.Append(c); } else if (c is ',' or '.' or '/' or '\\' or '=' or ' ' or ';') { if (!previousSpace && sb.Length > 0) previousSpace = true; } else { var swap = ConvertEdgeCases(c, toLower); if (swap != null) { if (previousSpace) { sb.Append(' '); previousSpace = false; } sb.Append(swap); } } if (maxLength != null && sb.Length == maxLength) break; } return sb.ToString(); } public static string RandomLowerCaseString(int length) { // ReSharper disable once StringLiteralTypo var chars = "abcdefghijklmnopqrstuvwxyz"; var stringChars = new char[length]; var random = new Random(); for (var i = 0; i < stringChars.Length; i++) stringChars[i] = chars[random.Next(chars.Length)]; return new string(stringChars); } }