IdentityShroud/IdentityShroud.Core/Helpers/SlugHelper.cs

85 lines
2.7 KiB
C#
Raw Normal View History

2026-02-08 18:00:24 +01:00
using System;
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 sha256 = SHA256.Create())
{
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
// Take first 4 bytes (will become ~5-6 base64url chars)
string base64Url = WebEncoders.Base64UrlEncode(hash, 0, 4);
return "-" + base64Url;
}
}
}