Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moves to the latest version of C# #120

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
19 changes: 9 additions & 10 deletions src/Slugify.Core/ISlugHelper.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
namespace Slugify
namespace Slugify;

public interface ISlugHelper
{
public interface ISlugHelper
{
/// <summary>
/// Generates a slug from the provided <paramref name="inputString"/>
/// </summary>
/// <param name="inputString">The string to slugify</param>
/// <returns>A slugified version of <paramref name="inputString"/></returns>
string GenerateSlug(string inputString);
}
/// <summary>
/// Generates a slug from the provided <paramref name="inputString"/>
/// </summary>
/// <param name="inputString">The string to slugify</param>
/// <returns>A slugified version of <paramref name="inputString"/></returns>
string GenerateSlug(string inputString);
}
278 changes: 136 additions & 142 deletions src/Slugify.Core/SlugHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,212 +4,206 @@
using System.Text;
using System.Text.RegularExpressions;

namespace Slugify
namespace Slugify;


public class SlugHelper(SlugHelperConfiguration config) : ISlugHelper
{
private static readonly Dictionary<string, Regex> _deleteRegexMap = [];
private static readonly Lazy<SlugHelperConfiguration> _defaultConfig = new(() => new SlugHelperConfiguration());

public class SlugHelper : ISlugHelper
{
private static readonly Dictionary<string, Regex> _deleteRegexMap = new Dictionary<string, Regex>();
private static readonly Lazy<SlugHelperConfiguration> _defaultConfig = new Lazy<SlugHelperConfiguration>(() => new SlugHelperConfiguration());
protected SlugHelperConfiguration Config { get; set; } = config ?? throw new ArgumentNullException(nameof(config), "can't be null use default config or empty constructor.");

protected SlugHelperConfiguration Config { get; set; }
public SlugHelper() : this(_defaultConfig.Value) { }

public SlugHelper() : this(_defaultConfig.Value) { }
/// <summary>
/// Implements <see cref="ISlugHelper.GenerateSlug(string)"/>
/// </summary>
public virtual string GenerateSlug(string inputString)
{
var sb = new StringBuilder();

// First we trim and lowercase if necessary
PrepareStringBuilder(inputString.Normalize(NormalizationForm.FormD), sb);
ApplyStringReplacements(sb);
RemoveNonSpacingMarks(sb);

public SlugHelper(SlugHelperConfiguration config)
if (Config.DeniedCharactersRegex == null)
{
Config = config ?? throw new ArgumentNullException(nameof(config), "can't be null use default config or empty constructor.");
RemoveNotAllowedCharacters(sb);
}

/// <summary>
/// Implements <see cref="ISlugHelper.GenerateSlug(string)"/>
/// </summary>
public virtual string GenerateSlug(string inputString)
// For backwards compatibility
if (Config.DeniedCharactersRegex != null)
{
var sb = new StringBuilder();

// First we trim and lowercase if necessary
PrepareStringBuilder(inputString.Normalize(NormalizationForm.FormD), sb);
ApplyStringReplacements(sb);
RemoveNonSpacingMarks(sb);

if (Config.DeniedCharactersRegex == null)
if (!_deleteRegexMap.TryGetValue(Config.DeniedCharactersRegex, out var deniedCharactersRegex))
{
RemoveNotAllowedCharacters(sb);
deniedCharactersRegex = new Regex(Config.DeniedCharactersRegex, RegexOptions.Compiled);
_deleteRegexMap.Add(Config.DeniedCharactersRegex, deniedCharactersRegex);
}

// For backwards compatibility
if (Config.DeniedCharactersRegex != null)
{
if (!_deleteRegexMap.TryGetValue(Config.DeniedCharactersRegex, out var deniedCharactersRegex))
{
deniedCharactersRegex = new Regex(Config.DeniedCharactersRegex, RegexOptions.Compiled);
_deleteRegexMap.Add(Config.DeniedCharactersRegex, deniedCharactersRegex);
}

var currentValue = sb.ToString();
sb.Clear();
sb.Append(DeleteCharacters(currentValue, deniedCharactersRegex));
}

if (Config.CollapseDashes)
{
CollapseDashes(sb);
}
var currentValue = sb.ToString();
sb.Clear();
sb.Append(DeleteCharacters(currentValue, deniedCharactersRegex));
}

return sb.ToString();
if (Config.CollapseDashes)
{
CollapseDashes(sb);
}

private void PrepareStringBuilder(string inputString, StringBuilder sb)
return sb.ToString();
}

private void PrepareStringBuilder(string inputString, StringBuilder sb)
{
var seenFirstNonWhitespace = false;
var indexOfLastNonWhitespace = 0;
for (var i = 0; i < inputString.Length; i++)
{
var seenFirstNonWhitespace = false;
var indexOfLastNonWhitespace = 0;
for (var i = 0; i < inputString.Length; i++)
// first, clean whitepace
var c = inputString[i];
var isWhitespace = char.IsWhiteSpace(c);
if (!seenFirstNonWhitespace && isWhitespace)
{
// first, clean whitepace
var c = inputString[i];
var isWhitespace = char.IsWhiteSpace(c);
if (!seenFirstNonWhitespace && isWhitespace)
if (Config.TrimWhitespace)
{
if (Config.TrimWhitespace)
{
continue;
}
else
{
sb.Append(c);
}
continue;
}
else
{
seenFirstNonWhitespace = true;
if (!isWhitespace)
{
indexOfLastNonWhitespace = sb.Length;
}
else
{
c = ' ';
sb.Append(c);
}
}
else
{
seenFirstNonWhitespace = true;
if (!isWhitespace)
{
indexOfLastNonWhitespace = sb.Length;
}
else
{
c = ' ';

if (Config.CollapseWhiteSpace)
if (Config.CollapseWhiteSpace)
{
while ((i + 1) < inputString.Length && char.IsWhiteSpace(inputString[i + 1]))
{
while ((i + 1) < inputString.Length && char.IsWhiteSpace(inputString[i + 1]))
{
i++;
}
i++;
}
}
if (Config.ForceLowerCase)
{
c = char.ToLower(c);
}

sb.Append(c);
}
}
if (Config.ForceLowerCase)
{
c = char.ToLower(c);
}

if (Config.TrimWhitespace)
{
sb.Length = indexOfLastNonWhitespace + 1;
sb.Append(c);
}
}

private void ApplyStringReplacements(StringBuilder sb)
if (Config.TrimWhitespace)
{
foreach (var replacement in Config.StringReplacements)
{
var search = replacement.Key.Normalize(NormalizationForm.FormD);
var replace = replacement.Value.Normalize(NormalizationForm.FormD);
sb.Length = indexOfLastNonWhitespace + 1;
}
}

private void ApplyStringReplacements(StringBuilder sb)
{
foreach (var replacement in Config.StringReplacements)
{
var search = replacement.Key.Normalize(NormalizationForm.FormD);
var replace = replacement.Value.Normalize(NormalizationForm.FormD);

for (var i = 0; i < sb.Length; i++)
for (var i = 0; i < sb.Length; i++)
{
if (SubstringEquals(sb, i, search))
{
if (SubstringEquals(sb, i, search))
{
sb.Remove(i, search.Length);
sb.Insert(i, replace);
sb.Remove(i, search.Length);
sb.Insert(i, replace);

i += replace.Length - 1;
}
i += replace.Length - 1;
}
}
}
}

private static bool SubstringEquals(StringBuilder sb, int index, string toMatch)
private static bool SubstringEquals(StringBuilder sb, int index, string toMatch)
{
if (sb.Length - index < toMatch.Length)
{
if (sb.Length - index < toMatch.Length)
return false;
}

for (var i = index; i < sb.Length; i++)
{
var matchIndex = i - index;

if (matchIndex == toMatch.Length)
{
return false;
return true;
}

for (var i = index; i < sb.Length; i++)
else if (sb[i] != toMatch[matchIndex])
{
var matchIndex = i - index;

if (matchIndex == toMatch.Length)
{
return true;
}
else if (sb[i] != toMatch[matchIndex])
{
return false;
}
return false;
}
return (sb.Length - index) == toMatch.Length;
}
return (sb.Length - index) == toMatch.Length;
}

// Thanks http://stackoverflow.com/a/249126!
protected static void RemoveNonSpacingMarks(StringBuilder sb)
// Thanks http://stackoverflow.com/a/249126!
protected static void RemoveNonSpacingMarks(StringBuilder sb)
{
for (var ich = 0; ich < sb.Length; ich++)
{
for (var ich = 0; ich < sb.Length; ich++)
if (CharUnicodeInfo.GetUnicodeCategory(sb[ich]) == UnicodeCategory.NonSpacingMark)
{
if (CharUnicodeInfo.GetUnicodeCategory(sb[ich]) == UnicodeCategory.NonSpacingMark)
{
sb.Remove(ich, 1);
ich--;
}
sb.Remove(ich, 1);
ich--;
}
}
}

protected void RemoveNotAllowedCharacters(StringBuilder sb)
protected void RemoveNotAllowedCharacters(StringBuilder sb)
{
// perf!
var allowedChars = Config.AllowedChars;
for (var i = 0; i < sb.Length; i++)
{
// perf!
var allowedChars = Config.AllowedChars;
for (var i = 0; i < sb.Length; i++)
if (!allowedChars.Contains(sb[i]))
{
if (!allowedChars.Contains(sb[i]))
{
sb.Remove(i, 1);
i--;
}
sb.Remove(i, 1);
i--;
}
}
}

protected static void CollapseDashes(StringBuilder sb)
protected static void CollapseDashes(StringBuilder sb)
{
var firstDash = true;
for (var i = 0; i < sb.Length; i++)
{
var firstDash = true;
for (var i = 0; i < sb.Length; i++)
// first, clean whitepace
if (sb[i] == '-')
{
// first, clean whitepace
if (sb[i] == '-')
if (firstDash)
{
if (firstDash)
{
firstDash = false;
}
else
{
sb.Remove(i, 1);
i--;
}
firstDash = false;
}
else
{
firstDash = true;
sb.Remove(i, 1);
i--;
}
}
else
{
firstDash = true;
}
}

protected static string DeleteCharacters(string str, Regex deniedCharactersRegex) => deniedCharactersRegex.Replace(str, string.Empty);
}

protected static string DeleteCharacters(string str, Regex deniedCharactersRegex) => deniedCharactersRegex.Replace(str, string.Empty);
}

Loading