Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
- 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.
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar issue: "to prevent generated slug to start/end" should be either "to prevent the generated slug from starting/ending" or "to prevent generated slugs from starting/ending".

Suggested change
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.
Trim options to remove characters from the input before replacements and character filtering. This is useful to prevent the generated slug from starting or ending with certain characters like dot.

Copilot uses AI. Check for mistakes.

- 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).
Comment on lines +221 to +228
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states these options remove characters "from the input before replacements and character filtering", but issue 162 describes wanting to remove trailing punctuation from the generated slug (i.e., the output). The current implementation trims early in the process, which may not match user expectations. Consider updating the documentation to clarify this behavior, or moving the trim operations to apply to the final slug output instead.

Copilot uses AI. Check for mistakes.

- 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."
```
3 changes: 3 additions & 0 deletions src/Slugify.Core/SlugHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user sets both TrimChars and TrimEndChars (or TrimStartChars), the operations will be applied sequentially, which could lead to unexpected behavior. For example, if TrimChars = ['.'] and TrimEndChars = ['_'], after TrimChars removes dots from both ends, TrimEndChars would then remove underscores. Consider documenting this interaction, or detecting and warning about conflicting configurations, or applying only the most specific option when multiple are set.

Suggested change
normalizedInput = Config.TrimChars.Length != 0 ? normalizedInput.Trim(Config.TrimChars) : normalizedInput;
normalizedInput = Config.TrimChars.Length != 0 && Config.TrimEndChars.Length == 0 && Config.TrimStartChars.Length == 0
? normalizedInput.Trim(Config.TrimChars)
: normalizedInput;

Copilot uses AI. Check for mistakes.
normalizedInput = Config.TrimEndChars.Length != 0 ? normalizedInput.TrimEnd(Config.TrimEndChars) : normalizedInput;
normalizedInput = Config.TrimStartChars.Length != 0 ? normalizedInput.TrimStart(Config.TrimStartChars) : normalizedInput;
Comment on lines +41 to +43
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trim operations are applied early in the pipeline, before string replacements and character filtering. This means they cannot trim characters that appear at the edges as a result of transformations. For example, if a string replacement adds a dot at the end, or if character filtering removes characters that expose trailing punctuation, the current implementation won't trim them. The MaximumLength feature (lines 92-100) sets a precedent by trimming trailing dashes AFTER all transformations. Consider moving these trim operations to after line 100 (before the return) to trim from the final slug, which would be more consistent and handle more use cases.

Copilot uses AI. Check for mistakes.

var sb = new StringBuilder(normalizedInput);

Expand Down
15 changes: 15 additions & 0 deletions src/Slugify.Core/SlugHelperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ public class SlugHelperConfiguration
/// </summary>
public bool TrimWhitespace { get; set; } = true;

/// <summary>
/// List of characters to be trimmed (removed from both ends) from generated slug
/// </summary>
public char[] TrimChars { get; set; } = [];

/// <summary>
/// List of characters to be trimmed from end of the generated slug
/// </summary>
public char[] TrimEndChars { get; set; } = [];

/// <summary>
/// List of characters to be trimmed from start of the generated slug
Comment on lines +65 to +75
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says "List of characters to be trimmed (removed from both ends) from generated slug" but the trimming happens on the input, not the generated slug. Consider changing to "from the input string" or "before slug generation" for accuracy. Similarly for TrimEndChars and TrimStartChars documentation.

Suggested change
/// List of characters to be trimmed (removed from both ends) from generated slug
/// </summary>
public char[] TrimChars { get; set; } = [];
/// <summary>
/// List of characters to be trimmed from end of the generated slug
/// </summary>
public char[] TrimEndChars { get; set; } = [];
/// <summary>
/// List of characters to be trimmed from start of the generated slug
/// List of characters to be trimmed (removed from both ends) from the input string before slug generation.
/// </summary>
public char[] TrimChars { get; set; } = [];
/// <summary>
/// List of characters to be trimmed from the end of the input string before slug generation.
/// </summary>
public char[] TrimEndChars { get; set; } = [];
/// <summary>
/// List of characters to be trimmed from the start of the input string before slug generation.

Copilot uses AI. Check for mistakes.
/// </summary>
public char[] TrimStartChars { get; set; } = [];

/// <summary>
/// Represents the maximum length of the slug. If the slug is longer than this value, it will be truncated.
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions tests/Slugify.Core.Tests/SlugHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Comment on lines +515 to +528
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only validates trimming characters that exist in the original input. Add a test case where trailing characters appear due to transformations, such as: input "test." with StringReplacements of {".": "-"} and TrimEndChars = ['-']. This would expose whether the trim operations correctly handle characters created during the slug generation process. The current implementation would fail such a test because trimming happens before replacements.

Copilot uses AI. Check for mistakes.

[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)
Expand Down
Loading