84 lines
No EOL
2.7 KiB
C#
84 lines
No EOL
2.7 KiB
C#
using System.Globalization;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
|
|
namespace IdentityShroud.Core.Helpers;
|
|
|
|
public static class SlugHelper
|
|
{
|
|
public static string GenerateSlug(string text, int maxLength = 40)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
return string.Empty;
|
|
|
|
// Normalize to decomposed form (separates accents from letters)
|
|
string normalized = text.Normalize(NormalizationForm.FormD);
|
|
|
|
StringBuilder sb = new StringBuilder(normalized.Length);
|
|
bool lastWasHyphen = false;
|
|
|
|
foreach (char c in normalized)
|
|
{
|
|
// Skip diacritics (accents)
|
|
if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.NonSpacingMark)
|
|
continue;
|
|
|
|
char lower = char.ToLowerInvariant(c);
|
|
|
|
// Convert valid characters
|
|
if ((lower >= 'a' && lower <= 'z') || (lower >= '0' && lower <= '9'))
|
|
{
|
|
sb.Append(lower);
|
|
lastWasHyphen = false;
|
|
}
|
|
// Convert spaces, underscores, and other separators to hyphen
|
|
else if (char.IsWhiteSpace(c) || c == '_' || c == '-')
|
|
{
|
|
if (!lastWasHyphen && sb.Length > 0)
|
|
{
|
|
sb.Append('-');
|
|
lastWasHyphen = true;
|
|
}
|
|
}
|
|
// Skip all other characters
|
|
}
|
|
|
|
// Trim trailing hyphen if any
|
|
if (sb.Length > 0 && sb[sb.Length - 1] == '-')
|
|
sb.Length--;
|
|
|
|
string slug = sb.ToString();
|
|
|
|
// Handle truncation with hash suffix for long strings
|
|
if (slug.Length > maxLength)
|
|
{
|
|
// Generate hash of original text
|
|
string hashSuffix = GenerateHashSuffix(text);
|
|
int contentLength = maxLength - hashSuffix.Length;
|
|
|
|
// Truncate at word boundary if possible
|
|
int cutPoint = contentLength;
|
|
int lastHyphen = slug.LastIndexOf('-', contentLength - 1);
|
|
|
|
if (lastHyphen > contentLength / 2)
|
|
cutPoint = lastHyphen;
|
|
|
|
slug = slug.Substring(0, cutPoint).TrimEnd('-') + hashSuffix;
|
|
}
|
|
|
|
return slug;
|
|
}
|
|
|
|
private static string GenerateHashSuffix(string text)
|
|
{
|
|
using (var md5 = MD5.Create())
|
|
{
|
|
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(text));
|
|
|
|
// Take first 4 bytes (will become ~5-6 base64url chars)
|
|
string base64Url = WebEncoders.Base64UrlEncode(hash, 0, 4);
|
|
return "-" + base64Url;
|
|
}
|
|
}
|
|
} |