2010-05-27 28 views
65

Vì vậy, tôi đã tìm kiếm và duyệt qua thẻ slug trên SO và chỉ tìm thấy hai giải pháp hấp dẫn:Thuật toán URL Slugify trong C#?

nào là nhưng giải pháp một phần vấn đề. Tôi có thể tự mình viết mã này lên nhưng tôi ngạc nhiên vì chưa có giải pháp nào.

Vì vậy, có triển khai thực hiện alrogithm slugify trong C# và/hoặc .NET đúng địa chỉ các ký tự latin, unicode và các vấn đề ngôn ngữ khác nhau đúng không?

+0

có nghĩa là gì để "slugify"? –

+6

slugify = làm cho chuỗi do người dùng gửi an toàn để sử dụng như một phần của URL ... hoặc cơ sở dữ liệu hoặc bất kỳ thứ gì nhưng thường là URL. – chakrit

Trả lời

123

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); 
} 
+0

Liên kết được đăng thỏa mãn câu hỏi của OP một cách độc đáo. –

+6

Mục đích của độ dài và cắt xén vượt quá 45 ký tự là gì? – Neil

+7

Giải pháp sẽ không hoạt động đối với bảng chữ cái Latin. Phương thức RemoveAccent sẽ loại bỏ ví dụ các ký tự Cyrillic. Hãy thử một cái gì đó như RemoveAccent ("Не работает") và kết quả sẽ là chuỗi rỗng: D – Evereq

3

Một vấn đề tôi đã có với slugification (từ mới!) Là va chạm. Ví dụ: nếu tôi có một bài đăng trên blog, được gọi là "Stack-Overflow" và được gọi là "Stack Overflow", các sên của hai tên đó giống nhau. Do đó, máy phát điện sên của tôi thường phải liên quan đến cơ sở dữ liệu theo một cách nào đó. Đây có thể là lý do tại sao bạn không thấy các giải pháp chung chung hơn ở đó.

+4

Cá nhân tôi thích nối thêm các sên với một mã định danh duy nhất (tức là một số nguyên) vào đảm bảo chúng là duy nhất. Nó không phải là giải pháp thân thiện nhất nhưng nó giúp tôi tránh xa rắc rối. –

+0

chỉ là một nhận xét, từ một điểm SEO một lượt xem, url và tiêu đề phải là duy nhất cho mỗi trang. – Dementic

16

Ở đây bạn tìm thấy một cách để generate url slug in c#. Chức năng này loại bỏ tất cả dấu (Marcel câu trả lời), thay thế khoảng trống, loại bỏ ký tự không hợp lệ, cắt dấu gạch ngang từ cuối và thay thế lần xuất hiện gấp đôi "-" hoặc "_"

Code:

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 ; 
    } 
11

Dưới đây là màn biểu diễn của tôi dựa trên câu trả lời của Joan và Marcel. Những thay đổi tôi đã thực hiện như sau:

  • Sử dụng phương pháp widely accepted để xóa dấu trọng âm.
  • Bộ nhớ đệm Regex rõ ràng để cải thiện tốc độ khiêm tốn.
  • Dấu phân tách từ khác được nhận dạng và chuẩn hóa thành dấu gạch nối.

Đây là mã:

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)); 
    } 
} 

này vẫn không giải quyết được vấn đề nhân vật phi latin. Một giải pháp hoàn toàn thay thế sẽ được sử dụng Uri.EscapeDataString để chuyển đổi chuỗi đại diện hex của nó:

string original = "测试公司"; 

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

Sau đó, sử dụng dữ liệu để tạo ra một siêu liên kết:

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

Nhiều trình duyệt sẽ hiển thị chữ Hán trong địa chỉ thanh (xem bên dưới), nhưng dựa trên thử nghiệm giới hạn của tôi, nó không được hỗ trợ hoàn toàn.

address bar with Chinese characters

LƯU Ý: Để Uri.EscapeDataString để làm việc theo cách này, iriParsing phải được bật.


EDIT

Đối với những người tìm kiếm để tạo URL sên trong C#, tôi khuyên bạn nên kiểm tra ra câu hỏi có liên quan này:

How does Stack Overflow generate its SEO-friendly URLs?

Đó là những gì tôi đã kết thúc sử dụng cho tôi dự án.

2

Đây là ảnh của tôi tại đó. Nó hỗ trợ:

  • loại bỏ các dấu (vì vậy chúng tôi không chỉ loại bỏ "không hợp lệ" ký tự) chiều dài
  • tối đa cho kết quả (hoặc trước khi loại bỏ các dấu - "truncate sớm")
  • tùy chỉnh separator giữa khối bình thường
  • kết quả có thể bị buộc phải viết hoa hoặc chữ thường
  • danh sách cấu hình các loại unicode hỗ trợ
  • danh sách cấu hình của dãy ký tự cho phép
  • hỗ trợ khuôn khổ 2,0

Code:

/// <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(); 
    } 
}