diff --git a/Benchmarks/Benchmark.cs b/Benchmarks/Benchmark.cs
index 786add2..c0ac8b1 100644
--- a/Benchmarks/Benchmark.cs
+++ b/Benchmarks/Benchmark.cs
@@ -140,5 +140,14 @@ public string ToTitleCase()
{
return input.ToTitleCase();
}
+
+ ///
+ /// Benchmark for the Slugify method.
+ ///
+ [Benchmark]
+ public string Slugify()
+ {
+ return input.Slugify();
+ }
}
}
\ No newline at end of file
diff --git a/StringExtension/Casing/Casing.cs b/StringExtension/Casing/Casing.cs
index 1dbd981..c0b12ee 100644
--- a/StringExtension/Casing/Casing.cs
+++ b/StringExtension/Casing/Casing.cs
@@ -1,4 +1,5 @@
using System.Buffers;
+using System.Globalization;
using System.Text;
using StringExtension.Internal;
@@ -388,4 +389,75 @@ private static bool IsEnglishMinorWord(ReadOnlySpan word)
return false;
}
+
+ ///
+ /// Converts the given string into a URL-friendly slug: lowercase, with accents
+ /// removed and every run of non-alphanumeric characters collapsed into a single
+ /// .
+ ///
+ /// The input string.
+ /// The character used to join words. Defaults to -.
+ /// The slugified string.
+ ///
+ /// Accented Latin letters are converted to their unaccented equivalent (e.g.
+ /// "é" becomes "e") via Unicode decomposition. Letters from non-Latin scripts
+ /// (e.g. Cyrillic, CJK) are lowercased and kept as-is rather than being
+ /// stripped or transliterated. Returns if
+ /// is or empty.
+ ///
+ public static string Slugify(this string input, char separator = '-')
+ {
+ if (string.IsNullOrEmpty(input))
+ {
+ return string.Empty;
+ }
+
+ ReadOnlySpan normalized = input.Normalize(NormalizationForm.FormD);
+
+ char[]? pooledBuffer = null;
+ Span buffer = (uint)normalized.Length <= BufferLimits.StackAllocThreshold
+ ? stackalloc char[normalized.Length]
+ : (pooledBuffer = ArrayPool.Shared.Rent(normalized.Length));
+
+ try
+ {
+ var count = 0;
+ var pendingSeparator = false;
+
+ foreach (var rune in normalized.EnumerateRunes())
+ {
+ if (Rune.GetUnicodeCategory(rune) == UnicodeCategory.NonSpacingMark)
+ {
+ // A combining accent mark produced by decomposition (e.g. the
+ // acute accent split off "é"). The base letter it modifies was
+ // already appended; drop the mark itself.
+ continue;
+ }
+
+ if (Rune.IsLetterOrDigit(rune))
+ {
+ if (pendingSeparator && count > 0)
+ {
+ buffer[count++] = separator;
+ }
+
+ count += Rune.ToLowerInvariant(rune).EncodeToUtf16(buffer[count..]);
+ pendingSeparator = false;
+ }
+ else
+ {
+ pendingSeparator = true;
+ }
+ }
+
+ return new string(buffer[..count]);
+ }
+ finally
+ {
+ if (pooledBuffer is not null)
+ {
+ ArrayPool.Shared.Return(pooledBuffer);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/UnitTests/StringExtensionTests.cs b/UnitTests/StringExtensionTests.cs
index 19f81b3..dc53768 100644
--- a/UnitTests/StringExtensionTests.cs
+++ b/UnitTests/StringExtensionTests.cs
@@ -329,4 +329,53 @@ public void TestToTitleCase_LastWordAlwaysCapitalized()
string result = input.ToTitleCase(useEnglishMinorWordRules: true);
Assert.That(result, Is.EqualTo(expected));
}
+
+
+ ///
+ /// Tests the Slugify method with accented characters and punctuation.
+ ///
+ [Test]
+ public void TestSlugify()
+ {
+ string input = "Café de la Gare! 2024";
+ string expected = "cafe-de-la-gare-2024";
+ string result = input.Slugify();
+ Assert.That(result, Is.EqualTo(expected));
+ }
+
+ ///
+ /// Tests that Slugify supports a custom separator.
+ ///
+ [Test]
+ public void TestSlugify_CustomSeparator()
+ {
+ string input = "Hello World";
+ string expected = "hello_world";
+ string result = input.Slugify('_');
+ Assert.That(result, Is.EqualTo(expected));
+ }
+
+ ///
+ /// Tests that Slugify does not produce leading or trailing separators
+ /// when the input starts or ends with punctuation.
+ ///
+ [Test]
+ public void TestSlugify_NoLeadingOrTrailingSeparator()
+ {
+ string input = "!!!Hello World!!!";
+ string expected = "hello-world";
+ string result = input.Slugify();
+ Assert.That(result, Is.EqualTo(expected));
+ }
+
+ ///
+ /// Tests that Slugify returns an empty string for a null input.
+ ///
+ [Test]
+ public void TestSlugify_NullInput()
+ {
+ string input = null!;
+ string result = input.Slugify();
+ Assert.That(result, Is.EqualTo(string.Empty));
+ }
}
\ No newline at end of file