C #의 URL Slugify 알고리즘?


86

그래서 나는 SO 의 슬러그 태그를 검색하고 탐색했으며 두 가지 매력적인 솔루션 만 찾았습니다.

문제에 대한 부분적인 해결책입니다. 수동으로 직접 코딩 할 수 있지만 아직 해결책이 없다는 사실에 놀랐습니다.

그렇다면 라틴 문자, 유니 코드 및 기타 다양한 언어 문제를 올바르게 해결하는 C # 및 / 또는 .NET의 slugify 알고리즘 구현이 있습니까?


"slugify"란 무엇을 의미합니까?
Billy ONeal

7
slugify = 사용자가 제출 한 문자열을 URL 또는 데이터베이스 또는 일반적으로 URL의 일부로 안전하게 사용할 수 있도록합니다.
chakrit

답변:


158

http://predicatet.blogspot.com/2009/04/improved-c-slug-generator-or-how-to.html

public static string GenerateSlug(this string phrase) 
{ 
    string str = phrase.RemoveAccent().ToLower(); 
    // invalid chars           
    str = Regex.Replace(str, @"[^a-z0-9\s-]", ""); 
    // convert multiple spaces into one space   
    str = Regex.Replace(str, @"\s+", " ").Trim(); 
    // cut and trim 
    str = str.Substring(0, str.Length <= 45 ? str.Length : 45).Trim();   
    str = Regex.Replace(str, @"\s", "-"); // hyphens   
    return str; 
} 

public static string RemoveAccent(this string txt) 
{ 
    byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt); 
    return System.Text.Encoding.ASCII.GetString(bytes); 
}

게시 된 링크는 OP의 질문을 멋지게 만족시킵니다.
Ian P

7
길이의 목적은 무엇이며 45 자 이상으로 잘립니다.

11
라틴 알파벳이 아닌 경우 솔루션이 작동하지 않습니다. RemoveAccent 메서드는 예를 들어 키릴 문자를 제거합니다. 빈 문자열을 RemoveAccent ( "Не работает") 같은 것을 시도하고 결과가 될 것입니다 : D
Evereq

이 솔루션을 좋아하고 이것을 사용하여 'ToSlug ()'라는 확장을 만들었습니다.
Yasser Shaikh

12
제발 ... 사용하지 마십시오 RemoveAccent. 방법에 대한이 질문을 확인하십시오 RemoveDiacritics. stackoverflow.com/questions/249087/…
Maxime Rouiller 2015

21

여기에서 C #에서 URL 슬러그를 생성하는 방법을 찾을 수 있습니다. 이 기능은 모든 악센트 (마르셀의 대답)를 제거하고, 공백을 교체하고, 유효하지 않은 문자를 제거하고, 끝에서 대시를 제거하고, "-"또는 "_"의 이중 발생을 교체합니다.

암호:

public static string ToUrlSlug(string value){

        //First to lower case
        value = value.ToLowerInvariant();

        //Remove all accents
        var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(value);
        value = Encoding.ASCII.GetString(bytes);

        //Replace spaces
        value = Regex.Replace(value, @"\s", "-", RegexOptions.Compiled);

        //Remove invalid chars
        value = Regex.Replace(value, @"[^a-z0-9\s-_]", "",RegexOptions.Compiled);

        //Trim dashes from end
        value = value.Trim('-', '_');

        //Replace double occurences of - or _
        value = Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled);

        return value ;
    }

17

다음은 Joan과 Marcel의 답변을 기반으로 한 내 표현입니다. 내가 변경 한 내용은 다음과 같습니다.

  • 악센트를 제거 하려면 널리 사용되는 방법을 사용하십시오 .
  • 적당한 속도 향상을위한 명시 적 정규식 캐싱.
  • 더 많은 단어 구분 기호가 인식되고 하이픈으로 정규화됩니다.

다음은 코드입니다.

public class UrlSlugger
{
    // white space, em-dash, en-dash, underscore
    static readonly Regex WordDelimiters = new Regex(@"[\s—–_]", RegexOptions.Compiled);

    // characters that are not valid
    static readonly Regex InvalidChars = new Regex(@"[^a-z0-9\-]", RegexOptions.Compiled);

    // multiple hyphens
    static readonly Regex MultipleHyphens = new Regex(@"-{2,}", RegexOptions.Compiled);

    public static string ToUrlSlug(string value)
    {
        // convert to lower case
        value = value.ToLowerInvariant();

        // remove diacritics (accents)
        value = RemoveDiacritics(value);

        // ensure all word delimiters are hyphens
        value = WordDelimiters.Replace(value, "-");

        // strip out invalid characters
        value = InvalidChars.Replace(value, "");

        // replace multiple hyphens (-) with a single hyphen
        value = MultipleHyphens.Replace(value, "-");

        // trim hyphens (-) from ends
        return value.Trim('-');
    }

    /// See: http://www.siao2.com/2007/05/14/2629747.aspx
    private static string RemoveDiacritics(string stIn)
    {
        string stFormD = stIn.Normalize(NormalizationForm.FormD);
        StringBuilder sb = new StringBuilder();

        for (int ich = 0; ich < stFormD.Length; ich++)
        {
            UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(stFormD[ich]);
            if (uc != UnicodeCategory.NonSpacingMark)
            {
                sb.Append(stFormD[ich]);
            }
        }

        return (sb.ToString().Normalize(NormalizationForm.FormC));
    }
}

이것은 여전히 ​​비 라틴 문자 문제를 해결하지 못합니다. 완전한 대안은 Uri.EscapeDataString 을 사용 하여 문자열을 16 진수 표현으로 변환하는 것입니다.

string original = "测试公司";

// %E6%B5%8B%E8%AF%95%E5%85%AC%E5%8F%B8
string converted = Uri.EscapeDataString(original);

그런 다음 데이터를 사용하여 하이퍼 링크를 생성합니다.

<a href="http://www.example.com/100/%E6%B5%8B%E8%AF%95%E5%85%AC%E5%8F%B8">
    测试公司
</a>

많은 브라우저에서 주소 표시 줄에 중국어 문자가 표시되지만 (아래 참조) 제한된 테스트에 따르면 완전히 지원되지는 않습니다.

중국어 문자가있는 주소 표시 줄

참고 : 위해서는 Uri.EscapeDataString가 이 방식으로 작동하는, iriParsing가 활성화되어 있어야합니다.


편집하다

C #에서 URL 슬러그를 생성하려는 사람들을 위해 다음 관련 질문을 확인하는 것이 좋습니다.

Stack Overflow는 SEO 친화적 인 URL을 어떻게 생성합니까?

내 프로젝트에 사용하게 된 것입니다.


4

슬러지 화 (새로운 단어!)와 관련하여 내가 가진 한 가지 문제는 충돌입니다. 예를 들어 "Stack-Overflow"라는 블로그 게시물과 "Stack Overflow"라는 블로그 게시물이있는 경우이 두 제목의 슬러그는 동일합니다. 따라서 내 슬러그 생성기는 일반적으로 어떤 방식 으로든 데이터베이스를 포함해야합니다. 이것이 더 일반적인 솔루션을 보지 못하는 이유 일 수 있습니다.


6
개인적으로 나는 슬러그에 고유 한 식별자 (예 : 정수)를 추가하여 고유한지 확인하는 것을 선호합니다. 가장 친절한 해결책은 아니지만 문제를 피하는 데 도움이됩니다.
Jeremy Cade 2010-06-21

1
SEO 관점에서보기, URL 및 제목은 각 페이지에 대해 고유해야합니다.
Rafael Herscovici

3

여기에 내 샷이 있습니다. 다음을 지원합니다.

  • 분음 부호 제거 ( "유효하지 않은"문자 만 제거하지 않음)
  • 결과의 최대 길이 (또는 분음 부호 제거 전- "early truncate")
  • 정규화 된 청크 사이의 사용자 지정 구분 기호
  • 결과는 대문자 또는 소문자로 강제 될 수 있습니다.
  • 지원되는 유니 코드 범주의 구성 가능한 목록
  • 허용되는 문자 범위의 구성 가능한 목록
  • 프레임 워크 2.0 지원

암호:

/// <summary>
/// Defines a set of utilities for creating slug urls.
/// </summary>
public static class Slug
{
    /// <summary>
    /// Creates a slug from the specified text.
    /// </summary>
    /// <param name="text">The text. If null if specified, null will be returned.</param>
    /// <returns>
    /// A slugged text.
    /// </returns>
    public static string Create(string text)
    {
        return Create(text, (SlugOptions)null);
    }

    /// <summary>
    /// Creates a slug from the specified text.
    /// </summary>
    /// <param name="text">The text. If null if specified, null will be returned.</param>
    /// <param name="options">The options. May be null.</param>
    /// <returns>A slugged text.</returns>
    public static string Create(string text, SlugOptions options)
    {
        if (text == null)
            return null;

        if (options == null)
        {
            options = new SlugOptions();
        }

        string normalised;
        if (options.EarlyTruncate && options.MaximumLength > 0 && text.Length > options.MaximumLength)
        {
            normalised = text.Substring(0, options.MaximumLength).Normalize(NormalizationForm.FormD);
        }
        else
        {
            normalised = text.Normalize(NormalizationForm.FormD);
        }
        int max = options.MaximumLength > 0 ? Math.Min(normalised.Length, options.MaximumLength) : normalised.Length;
        StringBuilder sb = new StringBuilder(max);
        for (int i = 0; i < normalised.Length; i++)
        {
            char c = normalised[i];
            UnicodeCategory uc = char.GetUnicodeCategory(c);
            if (options.AllowedUnicodeCategories.Contains(uc) && options.IsAllowed(c))
            {
                switch (uc)
                {
                    case UnicodeCategory.UppercaseLetter:
                        if (options.ToLower)
                        {
                            c = options.Culture != null ? char.ToLower(c, options.Culture) : char.ToLowerInvariant(c);
                        }
                        sb.Append(options.Replace(c));
                        break;

                    case UnicodeCategory.LowercaseLetter:
                        if (options.ToUpper)
                        {
                            c = options.Culture != null ? char.ToUpper(c, options.Culture) : char.ToUpperInvariant(c);
                        }
                        sb.Append(options.Replace(c));
                        break;

                    default:
                        sb.Append(options.Replace(c));
                        break;
                }
            }
            else if (uc == UnicodeCategory.NonSpacingMark)
            {
                // don't add a separator
            }
            else
            {
                if (options.Separator != null && !EndsWith(sb, options.Separator))
                {
                    sb.Append(options.Separator);
                }
            }

            if (options.MaximumLength > 0 && sb.Length >= options.MaximumLength)
                break;
        }

        string result = sb.ToString();

        if (options.MaximumLength > 0 && result.Length > options.MaximumLength)
        {
            result = result.Substring(0, options.MaximumLength);
        }

        if (!options.CanEndWithSeparator && options.Separator != null && result.EndsWith(options.Separator))
        {
            result = result.Substring(0, result.Length - options.Separator.Length);
        }

        return result.Normalize(NormalizationForm.FormC);
    }

    private static bool EndsWith(StringBuilder sb, string text)
    {
        if (sb.Length < text.Length)
            return false;

        for (int i = 0; i < text.Length; i++)
        {
            if (sb[sb.Length - 1 - i] != text[text.Length - 1 - i])
                return false;
        }
        return true;
    }
}

/// <summary>
/// Defines options for the Slug utility class.
/// </summary>
public class SlugOptions
{
    /// <summary>
    /// Defines the default maximum length. Currently equal to 80.
    /// </summary>
    public const int DefaultMaximumLength = 80;

    /// <summary>
    /// Defines the default separator. Currently equal to "-".
    /// </summary>
    public const string DefaultSeparator = "-";

    private bool _toLower;
    private bool _toUpper;

    /// <summary>
    /// Initializes a new instance of the <see cref="SlugOptions"/> class.
    /// </summary>
    public SlugOptions()
    {
        MaximumLength = DefaultMaximumLength;
        Separator = DefaultSeparator;
        AllowedUnicodeCategories = new List<UnicodeCategory>();
        AllowedUnicodeCategories.Add(UnicodeCategory.UppercaseLetter);
        AllowedUnicodeCategories.Add(UnicodeCategory.LowercaseLetter);
        AllowedUnicodeCategories.Add(UnicodeCategory.DecimalDigitNumber);
        AllowedRanges = new List<KeyValuePair<short, short>>();
        AllowedRanges.Add(new KeyValuePair<short, short>((short)'a', (short)'z'));
        AllowedRanges.Add(new KeyValuePair<short, short>((short)'A', (short)'Z'));
        AllowedRanges.Add(new KeyValuePair<short, short>((short)'0', (short)'9'));
    }

    /// <summary>
    /// Gets the allowed unicode categories list.
    /// </summary>
    /// <value>
    /// The allowed unicode categories list.
    /// </value>
    public virtual IList<UnicodeCategory> AllowedUnicodeCategories { get; private set; }

    /// <summary>
    /// Gets the allowed ranges list.
    /// </summary>
    /// <value>
    /// The allowed ranges list.
    /// </value>
    public virtual IList<KeyValuePair<short, short>> AllowedRanges { get; private set; }

    /// <summary>
    /// Gets or sets the maximum length.
    /// </summary>
    /// <value>
    /// The maximum length.
    /// </value>
    public virtual int MaximumLength { get; set; }

    /// <summary>
    /// Gets or sets the separator.
    /// </summary>
    /// <value>
    /// The separator.
    /// </value>
    public virtual string Separator { get; set; }

    /// <summary>
    /// Gets or sets the culture for case conversion.
    /// </summary>
    /// <value>
    /// The culture.
    /// </value>
    public virtual CultureInfo Culture { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the string can end with a separator string.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the string can end with a separator string; otherwise, <c>false</c>.
    /// </value>
    public virtual bool CanEndWithSeparator { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the string is truncated before normalization.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the string is truncated before normalization; otherwise, <c>false</c>.
    /// </value>
    public virtual bool EarlyTruncate { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether to lowercase the resulting string.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the resulting string must be lowercased; otherwise, <c>false</c>.
    /// </value>
    public virtual bool ToLower
    {
        get
        {
            return _toLower;
        }
        set
        {
            _toLower = value;
            if (_toLower)
            {
                _toUpper = false;
            }
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether to uppercase the resulting string.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the resulting string must be uppercased; otherwise, <c>false</c>.
    /// </value>
    public virtual bool ToUpper
    {
        get
        {
            return _toUpper;
        }
        set
        {
            _toUpper = value;
            if (_toUpper)
            {
                _toLower = false;
            }
        }
    }

    /// <summary>
    /// Determines whether the specified character is allowed.
    /// </summary>
    /// <param name="character">The character.</param>
    /// <returns>true if the character is allowed; false otherwise.</returns>
    public virtual bool IsAllowed(char character)
    {
        foreach (var p in AllowedRanges)
        {
            if (character >= p.Key && character <= p.Value)
                return true;
        }
        return false;
    }

    /// <summary>
    /// Replaces the specified character by a given string.
    /// </summary>
    /// <param name="character">The character to replace.</param>
    /// <returns>a string.</returns>
    public virtual string Replace(char character)
    {
        return character.ToString();
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.