From cd8243f2423ed0c3a5e2862e9dda57ad682b8766 Mon Sep 17 00:00:00 2001 From: Mariusz Jamro Date: Fri, 13 Feb 2026 21:32:34 +0100 Subject: [PATCH] Support for trimming chars from generated slug's start/end. --- README.md | 35 +++++++++++++++- src/Slugify.Core/SlugHelper.cs | 3 ++ src/Slugify.Core/SlugHelperConfiguration.cs | 15 +++++++ tests/Slugify.Core.Tests/SlugHelperTests.cs | 45 +++++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 11def20..b50fcd4 100644 --- a/README.md +++ b/README.md @@ -214,4 +214,37 @@ Specifying the `DeniedCharactersRegex` option will disable the character removal This will limit the length of the generated slug to be a maximum of the number of chars given by the parameter. If the truncation happens in a way that a trailing `-` is left, it will be removed. -- Default value: `null` \ No newline at end of file +- Default value: `null` + +### `TrimChars`, `TrimEndChars`, `TrimStartChars` + +Trim options to remove characters from the input before replacements and character filtering. This is useful to prevent generated slug to start/end with certain characters like dot. + +- Options + - `TrimChars` — characters to trim from both ends (behaves like `string.Trim(char[])`). + - `TrimEndChars` — characters to trim from the end (behaves like `string.TrimEnd(char[])`). + - `TrimStartChars` — characters to trim from the start (behaves like `string.TrimStart(char[])`). + +- Default value: all three are empty arrays (no extra trimming). + +- Example: + +```csharp +var helper = new SlugHelper(new SlugHelperConfiguration +{ + TrimChars = ['.'] +}); +Console.WriteLine(helper.GenerateSlug(".hello world.")); // "hello-world" + +helper = new SlugHelper(new SlugHelperConfiguration +{ + TrimEndChars = ['.'] +}); +Console.WriteLine(helper.GenerateSlug(".hello world.")); // ".hello-world" + +helper = new SlugHelper(new SlugHelperConfiguration +{ + TrimStartChars = ['.'] +}); +Console.WriteLine(helper.GenerateSlug(".hello world.")); // "hello-world." +``` diff --git a/src/Slugify.Core/SlugHelper.cs b/src/Slugify.Core/SlugHelper.cs index 136090a..e64cb6c 100644 --- a/src/Slugify.Core/SlugHelper.cs +++ b/src/Slugify.Core/SlugHelper.cs @@ -38,6 +38,9 @@ public virtual string GenerateSlug(string inputString) normalizedInput = normalizedInput.Normalize(NormalizationForm.FormD); normalizedInput = Config.TrimWhitespace ? normalizedInput.Trim() : normalizedInput; normalizedInput = Config.ForceLowerCase ? normalizedInput.ToLowerInvariant() : normalizedInput; + normalizedInput = Config.TrimChars.Length != 0 ? normalizedInput.Trim(Config.TrimChars) : normalizedInput; + normalizedInput = Config.TrimEndChars.Length != 0 ? normalizedInput.TrimEnd(Config.TrimEndChars) : normalizedInput; + normalizedInput = Config.TrimStartChars.Length != 0 ? normalizedInput.TrimStart(Config.TrimStartChars) : normalizedInput; var sb = new StringBuilder(normalizedInput); diff --git a/src/Slugify.Core/SlugHelperConfiguration.cs b/src/Slugify.Core/SlugHelperConfiguration.cs index 3874aef..ebde1c0 100644 --- a/src/Slugify.Core/SlugHelperConfiguration.cs +++ b/src/Slugify.Core/SlugHelperConfiguration.cs @@ -61,6 +61,21 @@ public class SlugHelperConfiguration /// public bool TrimWhitespace { get; set; } = true; + /// + /// List of characters to be trimmed (removed from both ends) from generated slug + /// + public char[] TrimChars { get; set; } = []; + + /// + /// List of characters to be trimmed from end of the generated slug + /// + public char[] TrimEndChars { get; set; } = []; + + /// + /// List of characters to be trimmed from start of the generated slug + /// + public char[] TrimStartChars { get; set; } = []; + /// /// Represents the maximum length of the slug. If the slug is longer than this value, it will be truncated. /// diff --git a/tests/Slugify.Core.Tests/SlugHelperTests.cs b/tests/Slugify.Core.Tests/SlugHelperTests.cs index e711ca9..3c1a5c7 100644 --- a/tests/Slugify.Core.Tests/SlugHelperTests.cs +++ b/tests/Slugify.Core.Tests/SlugHelperTests.cs @@ -512,6 +512,51 @@ public void TestNoTrimmingWithLeadingAndTrailingSpaces(ISlugHelper helper) Assert.Equal(expected, helper.GenerateSlug(original)); } + [Theory] + [MemberData(nameof(GenerateStandardSluggers))] + public void TestTrimmingEndChars(ISlugHelper helper) + { + const string original = "hello._world._"; + const string expected = "hello._world"; + + helper.Config = new SlugHelperConfiguration + { + TrimEndChars = [ '.', '_' ], + }; + + Assert.Equal(expected, helper.GenerateSlug(original)); + } + + [Theory] + [MemberData(nameof(GenerateStandardSluggers))] + public void TestTrimmingStartChars(ISlugHelper helper) + { + const string original = "._hello._world"; + const string expected = "hello._world"; + + helper.Config = new SlugHelperConfiguration + { + TrimStartChars = [ '.', '_' ], + }; + + Assert.Equal(expected, helper.GenerateSlug(original)); + } + + [Theory] + [MemberData(nameof(GenerateStandardSluggers))] + public void TestTrimmingChars(ISlugHelper helper) + { + const string original = "._hello._world._"; + const string expected = "hello._world"; + + helper.Config = new SlugHelperConfiguration + { + TrimChars = [ '.', '_' ], + }; + + Assert.Equal(expected, helper.GenerateSlug(original)); + } + [Theory] [MemberData(nameof(GenerateStandardSluggers))] public void TestReplacementWithEmptyStringThenCollapsing(ISlugHelper helper)