Pointless Waymarks Tools

Artifact [5d9faef110]
Login

Artifact [5d9faef110]

Artifact 5d9faef110f61be4dd2176e5fbd1533c8d931c83bfc0ae3cf859c26f01aa03ff:


using System.Text;

namespace PointlessWaymarks.CommonTools;

public static class SlugTools
{
    private static string? ConvertEdgeCases(char c, bool toLower)
    {
        var swap = c switch
        {
            'ı' => "i",
            'ł' => "l",
            'Ł' => toLower ? "l" : "L",
            'đ' => "d",
            'ß' => "ss",
            'ø' => "o",
            'Þ' => "th",
            _ => null
        };

        return swap;
    }

    /// <summary>
    ///     This is intended for use in the live processing of user input where you want to create slug like strings but to be
    ///     friendly to typed input (for example so trailing spaces must be allowed to avoid fighting the user) - in general
    ///     this is not as strict as CreateSpacedString.
    /// </summary>
    /// <param name="toLower"></param>
    /// <param name="value"></param>
    /// <param name="allowedBeyondAtoZ1To9"></param>
    /// <returns></returns>
    public static string CreateRelaxedInputSpacedString(bool toLower, string? value,
        List<char>? allowedBeyondAtoZ1To9 = null)
    {
        if (value == null)
            return "";

        allowedBeyondAtoZ1To9 ??= [];

        var normalized = value.Normalize(NormalizationForm.FormKD);

        var len = normalized.Length;
        var sb = new StringBuilder(len);

        for (var i = 0; i < len; i++)
        {
            var c = normalized[i];
            if (c is >= 'a' and <= 'z' or >= '0' and <= '9' || allowedBeyondAtoZ1To9.Contains(c))
            {
                sb.Append(c);
            }
            else if (c is >= 'A' and <= 'Z')
            {
                // Tricky way to convert to lowercase
                if (toLower)
                    sb.Append((char)(c | 32));
                else
                    sb.Append(c);
            }
            else
            {
                var swap = ConvertEdgeCases(c, toLower);

                if (swap != null) sb.Append(swap);
            }
        }

        return sb.ToString();
    }

    /// <summary>
    ///     Creates a Slug.
    /// </summary>
    /// <param name="toLower"></param>
    /// <param name="values"></param>
    /// <returns></returns>
    public static string CreateSlug(bool toLower, params string[] values)
    {
        return CreateSlug(toLower, string.Join("-", values));
    }

    /// <summary>
    ///     Creates a slug.
    /// </summary>
    /// <param name="toLower"></param>
    /// <param name="value"></param>
    /// <param name="maxLength"></param>
    /// <returns></returns>
    //     References:
    //     https://stackoverflow.com/questions/25259/how-does-stack-overflow-generate-its-seo-friendly-urls
    //     http://www.unicode.org/reports/tr15/tr15-34.html
    //     https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
    //     https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
    //     https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string
    public static string CreateSlug(bool toLower, string? value, int maxLength = 100)
    {
        if (value == null)
            return "";

        var normalized = value.Normalize(NormalizationForm.FormKD);

        var len = normalized.Length;
        var prevDash = false;
        var sb = new StringBuilder(len);

        for (var i = 0; i < len; i++)
        {
            var c = normalized[i];
            if (c is >= 'a' and <= 'z' or >= '0' and <= '9')
            {
                if (prevDash)
                {
                    sb.Append('-');
                    prevDash = false;
                }

                sb.Append(c);
            }
            else if (c is >= 'A' and <= 'Z')
            {
                if (prevDash)
                {
                    sb.Append('-');
                    prevDash = false;
                }

                // Tricky way to convert to lowercase
                if (toLower)
                    sb.Append((char)(c | 32));
                else
                    sb.Append(c);
            }
            else if (c is ' ' or ',' or '.' or '/' or '\\' or '-' or '_' or '=')
            {
                if (!prevDash && sb.Length > 0) prevDash = true;
            }
            else
            {
                var swap = ConvertEdgeCases(c, toLower);

                if (swap != null)
                {
                    if (prevDash)
                    {
                        sb.Append('-');
                        prevDash = false;
                    }

                    sb.Append(swap);
                }
            }

            if (sb.Length == maxLength)
                break;
        }

        return sb.ToString();
    }

    /// <summary>
    ///     This method mimics the Create method but is focused on creating a spaced string with the intent that in some cases
    ///     this may create a format that communicates the same information and intent as the Create Slug method but is easier
    ///     to read and more user friendly.
    /// </summary>
    /// <param name="toLower"></param>
    /// <param name="value"></param>
    /// <param name="maxLength"></param>
    /// <returns></returns>
    public static string CreateSpacedString(bool toLower, string value, int? maxLength = 100)
    {
        var normalized = value.Normalize(NormalizationForm.FormKD);

        var len = normalized.Length;
        var previousSpace = false;
        var sb = new StringBuilder(len);

        for (var i = 0; i < len; i++)
        {
            var c = normalized[i];
            if (c is >= 'a' and <= 'z' or >= '0' and <= '9' or '_' or '-')
            {
                if (previousSpace)
                {
                    sb.Append(' ');
                    previousSpace = false;
                }

                sb.Append(c);
            }
            else if (c is >= 'A' and <= 'Z')
            {
                if (previousSpace)
                {
                    sb.Append(' ');
                    previousSpace = false;
                }

                // Tricky way to convert to lowercase
                if (toLower)
                    sb.Append((char)(c | 32));
                else
                    sb.Append(c);
            }
            else if (c is ',' or '.' or '/' or '\\' or '=' or ' ' or ';')
            {
                if (!previousSpace && sb.Length > 0) previousSpace = true;
            }
            else
            {
                var swap = ConvertEdgeCases(c, toLower);

                if (swap != null)
                {
                    if (previousSpace)
                    {
                        sb.Append(' ');
                        previousSpace = false;
                    }

                    sb.Append(swap);
                }
            }

            if (maxLength != null && sb.Length == maxLength)
                break;
        }

        return sb.ToString();
    }

    public static string RandomLowerCaseString(int length)
    {
        // ReSharper disable once StringLiteralTypo
        var chars = "abcdefghijklmnopqrstuvwxyz";
        var stringChars = new char[length];
        var random = new Random();

        for (var i = 0; i < stringChars.Length; i++) stringChars[i] = chars[random.Next(chars.Length)];

        return new string(stringChars);
    }
}