Stack Overflow สร้าง URL ที่เป็นมิตรกับ SEO ได้อย่างไร


253

นิพจน์ทั่วไปที่สมบูรณ์สมบูรณ์แบบคืออะไรหรือกระบวนการอื่นใดที่จะมีชื่อ:

คุณจะเปลี่ยนชื่อเป็นส่วนหนึ่งของ URL เช่น Stack Overflow ได้อย่างไร

และเปลี่ยนเป็น

how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow

ที่ใช้ใน URL ที่เป็นมิตรกับ SEO ใน Stack Overflow

สภาพแวดล้อมการพัฒนาที่ฉันใช้อยู่คือRuby on Railsแต่ถ้ามีโซลูชันเฉพาะแพลตฟอร์มอื่น ๆ (.NET, PHP, Django ) ฉันก็อยากเห็นสิ่งเหล่านี้เช่นกัน

ฉันแน่ใจว่าฉัน (หรือผู้อ่านคนอื่น) จะเจอปัญหาเดียวกันบนแพลตฟอร์มที่แตกต่างกันในบรรทัด

ฉันใช้เส้นทางที่กำหนดเองและฉันต้องการทราบวิธีการปรับเปลี่ยนสตริงเป็นตัวละครพิเศษทั้งหมดจะถูกลบออกมันเป็นตัวพิมพ์เล็กทั้งหมดและช่องว่างทั้งหมดถูกแทนที่


แล้วตัวละครตลกล่ะ? คุณจะทำอะไรกับสิ่งเหล่านั้น umlauts? เครื่องหมายวรรคตอน? สิ่งเหล่านี้ต้องได้รับการพิจารณา โดยพื้นฐานแล้วฉันจะใช้วิธี white-list ซึ่งตรงข้ามกับ black-list approach ด้านบน: อธิบายตัวละครที่คุณจะอนุญาตตัวละครตัวไหนที่คุณจะแปลง (เป็นอะไร?) แล้วเปลี่ยนส่วนที่เหลือเป็นความหมายแบบเต็ม ("") . ฉันสงสัยว่าคุณสามารถทำสิ่งนี้ได้ใน regex เดียว ... ทำไมไม่ลองวนผ่านตัวละครล่ะ?
Daren Thomas

1
ควรที่จะอพยพไปอยู่เมตา ; ตามคำถามและคำตอบทั้งสองข้อตกลงที่เฉพาะเจาะจงกับการนำไปใช้งาน SO และคำตอบที่ยอมรับนั้นมาจาก @JeffAtwood
casperOne

19
@casperOne คุณคิดว่า Jeff ไม่ได้รับอนุญาตจากชื่อเสียงที่ไม่ใช่เมตาดาต้าบ้างไหม? คำถามเกี่ยวกับ "เราจะทำสิ่งนี้ได้อย่างไร" โดยไม่เฉพาะเจาะจงว่าจะทำอย่างไรที่นี่ "
Paŭlo Ebermann

@ PaŭloEbermann: มันไม่ใช่เรื่องของเจฟฟ์ที่ได้รับชื่อเสียงที่ไม่ใช่เมตาดาต้า เนื้อหาของคำถามอ้างอิงการใช้งานของ StackOverflow โดยเฉพาะอย่างยิ่งเหตุผลในการใช้เมตา
casperOne

คำตอบ:


300

นี่คือวิธีที่เราทำ โปรดทราบว่าอาจมีเงื่อนไขขอบมากกว่าที่คุณเห็นได้อย่างรวดเร็วก่อน

นี่เป็นรุ่นที่สองซึ่งไม่ได้ควบคุมเพื่อประสิทธิภาพเพิ่มขึ้น 5 เท่า (และใช่ฉันเปรียบเทียบมัน) ฉันคิดว่าฉันจะปรับให้เหมาะสมเพราะฟังก์ชั่นนี้สามารถเรียกได้หลายร้อยครั้งต่อหน้า

/// <summary>
/// Produces optional, URL-friendly version of a title, "like-this-one". 
/// hand-tuned for speed, reflects performance refactoring contributed
/// by John Gietzen (user otac0n) 
/// </summary>
public static string URLFriendly(string title)
{
    if (title == null) return "";

    const int maxlen = 80;
    int len = title.Length;
    bool prevdash = false;
    var sb = new StringBuilder(len);
    char c;

    for (int i = 0; i < len; i++)
    {
        c = title[i];
        if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
        {
            sb.Append(c);
            prevdash = false;
        }
        else if (c >= 'A' && c <= 'Z')
        {
            // tricky way to convert to lowercase
            sb.Append((char)(c | 32));
            prevdash = false;
        }
        else if (c == ' ' || c == ',' || c == '.' || c == '/' || 
            c == '\\' || c == '-' || c == '_' || c == '=')
        {
            if (!prevdash && sb.Length > 0)
            {
                sb.Append('-');
                prevdash = true;
            }
        }
        else if ((int)c >= 128)
        {
            int prevlen = sb.Length;
            sb.Append(RemapInternationalCharToAscii(c));
            if (prevlen != sb.Length) prevdash = false;
        }
        if (i == maxlen) break;
    }

    if (prevdash)
        return sb.ToString().Substring(0, sb.Length - 1);
    else
        return sb.ToString();
}

หากต้องการดูรุ่นก่อนหน้าของรหัสนี้แทนที่ (แต่เทียบเท่ากับการทำงานและเร็วขึ้น 5x) ดูประวัติการแก้ไขของโพสต์นี้ (คลิกลิงค์วันที่)

นอกจากนี้RemapInternationalCharToAsciiรหัสที่มาวิธีการที่สามารถพบได้ที่นี่


24
มันจะดีกับเวอร์ชั่นที่ไม่เพียงแค่ลดอักขระที่เน้นเสียงเช่นåäö แต่แทนที่จะปล่อยให้พวกมันเป็น aao ... ^^
Oskar Duveborn

22
@ oskar ต้นขั้วของRemapInternationalCharToAscii()ฟังก์ชั่นนั้นมีmeta.stackexchange.com/questions/7435/…
Jeff Atwood

3
มันเยี่ยมมาก การเปลี่ยนแปลงเดียวที่ฉันทำไปแล้วคือเปลี่ยน "if (i == maxlen) break;" ที่จะกลายเป็น "if (sb.Length == maxlen) break;" ในกรณีที่มีอักขระที่ไม่ถูกต้องจำนวนมากในสายอักขระที่ฉันกำลังจะผ่านเข้ามา
Tom Chantler

4
การเพิ่มประสิทธิภาพเล็กน้อย: if (prevdash) sb.Length -= 1; return sb.ToString();แทนที่จะเป็นifคำสั่งสุดท้าย
Mark Hurd

8
@Dommer sb.Length == maxlen break;เป็นรถถ้าป้าย maxLenght-1 คือ "เอสเอส" จะได้รับการแปลง "ss" จะไม่เป็นจริงมันจะดีกว่าแทนเพื่อทดสอบsb.Length == maxlene (sb.Length > = maxlen)
Henrik Stenbæk

32

นี่คือรหัส Jeff ของฉัน ฉันได้ทำการเปลี่ยนแปลงต่อไปนี้:

  • ยัติภังค์ถูกผนวกเข้าด้วยวิธีที่สามารถเพิ่มและจากนั้นจำเป็นต้องลบออกเนื่องจากเป็นอักขระตัวสุดท้ายในสตริง นั่นคือเราไม่ต้องการ“ my-slug-” นี่หมายถึงการจัดสรรสตริงพิเศษเพื่อลบออกในกรณีขอบนี้ ฉันได้แก้ไขสิ่งนี้ด้วยการเลื่อนยัติภังค์ ถ้าคุณเปรียบเทียบรหัสของฉันกับ Jeff เหตุผลนี้ง่ายต่อการติดตาม
  • วิธีการของเขานั้นตามการค้นหาอย่างหมดจดและขาดตัวละครมากมายที่ฉันพบในตัวอย่างขณะทำการค้นคว้าใน Stack Overflow เพื่อตอบโต้สิ่งนี้ฉันต้องแสดงให้เห็นถึงการทำ normalization pass (การเปรียบเทียบ AKA ที่กล่าวถึงใน Meta Stack Overflow คำถามตัวละครที่ไม่ใช่ US-ASCII หลุดจาก URL เต็ม (โปรไฟล์) ) แล้วละเว้นอักขระใด ๆ ที่อยู่นอกช่วงที่ยอมรับได้ ใช้งานได้เกือบตลอดเวลา ...
  • ... เพราะเมื่อก่อนไม่ได้ฉันต้องเพิ่มตารางการค้นหาด้วย ดังที่ได้กล่าวไปแล้วอักขระบางตัวไม่แม็พกับค่า ASCII ที่ต่ำเมื่อทำการปรับมาตรฐาน แทนที่จะปล่อยสิ่งเหล่านี้ฉันมีรายการข้อยกเว้นด้วยตนเองที่เต็มไปด้วยหลุมอย่างไม่ต้องสงสัย แต่ดีกว่าไม่มีอะไร รหัสการปรับสภาพได้รับแรงบันดาลใจจากโพสต์ที่ยอดเยี่ยมของ Jon Hanna ในคำถาม Stack Overflow ฉันจะลบเครื่องหมายเน้นเสียงในสตริงได้อย่างไร .
  • การแปลงเคสเป็นทางเลือกด้วย

    public static class Slug
    {
        public static string Create(bool toLower, params string[] values)
        {
            return Create(toLower, String.Join("-", values));
        }
    
        /// <summary>
        /// Creates a slug.
        /// References:
        /// http://www.unicode.org/reports/tr15/tr15-34.html
        /// /meta/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
        /// /programming/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
        /// /programming/3769457/how-can-i-remove-accents-on-a-string
        /// </summary>
        /// <param name="toLower"></param>
        /// <param name="normalised"></param>
        /// <returns></returns>
        public static string Create(bool toLower, string value)
        {
            if (value == null)
                return "";
    
            var normalised = value.Normalize(NormalizationForm.FormKD);
    
            const int maxlen = 80;
            int len = normalised.Length;
            bool prevDash = false;
            var sb = new StringBuilder(len);
            char c;
    
            for (int i = 0; i < len; i++)
            {
                c = normalised[i];
                if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
                {
                    if (prevDash)
                    {
                        sb.Append('-');
                        prevDash = false;
                    }
                    sb.Append(c);
                }
                else if (c >= 'A' && c <= '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 == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=')
                {
                    if (!prevDash && sb.Length > 0)
                    {
                        prevDash = true;
                    }
                }
                else
                {
                    string swap = ConvertEdgeCases(c, toLower);
    
                    if (swap != null)
                    {
                        if (prevDash)
                        {
                            sb.Append('-');
                            prevDash = false;
                        }
                        sb.Append(swap);
                    }
                }
    
                if (sb.Length == maxlen)
                    break;
            }
            return sb.ToString();
        }
    
        static string ConvertEdgeCases(char c, bool toLower)
        {
            string swap = null;
            switch (c)
            {
                case 'ı':
                    swap = "i";
                    break;
                case 'ł':
                    swap = "l";
                    break;
                case 'Ł':
                    swap = toLower ? "l" : "L";
                    break;
                case 'đ':
                    swap = "d";
                    break;
                case 'ß':
                    swap = "ss";
                    break;
                case 'ø':
                    swap = "o";
                    break;
                case 'Þ':
                    swap = "th";
                    break;
            }
            return swap;
        }
    }

สำหรับรายละเอียดเพิ่มเติมทดสอบหน่วยและคำอธิบายว่าทำไมFacebook 's URLโครงการเป็นอย่างชาญฉลาดน้อยกว่ากองล้น, ฉันมีรุ่นที่ขยายตัวนี้ในบล็อกของฉัน


4
+1 นี่คือด่านที่ดี ฉันยังเพิ่มความคิดเห็นในบล็อกของคุณเกี่ยวกับอาจเปลี่ยนif (i == maxlen) break;เป็นif (sb.Length == maxlen) break;แทนดังนั้นถ้าคุณส่งผ่านสายอักขระที่มีช่องว่าง / อักขระที่ไม่ถูกต้องจำนวนมากคุณยังสามารถรับความยาวตามที่ต้องการในขณะที่รหัสที่ยืนอยู่อาจจบลง ตัดทอนอย่างหนาแน่น (เช่นพิจารณากรณีที่คุณเริ่มต้นด้วย 80 ช่องว่าง ... ) และเกณฑ์มาตรฐานคร่าวๆ 10,000,000 ซ้ำกับรหัสของเจฟฟ์แสดงให้เห็นว่ามันเป็นความเร็วเดียวกัน
Tom Chantler

1
ขอขอบคุณตอบกลับในบล็อกของฉันและแก้ไขรหัสที่นั่นและข้างบน ขอขอบคุณสำหรับการเปรียบเทียบโค้ด สำหรับผู้ที่สนใจมันเป็นสิ่งที่เท่าเทียมกับเจฟฟ์
DanH

2
ดูเหมือนว่ามีปัญหาบางอย่างกับ Slug.Create (): ÆØÅเวอร์ชันตัวพิมพ์ใหญ่ไม่ถูกแปลงอย่างถูกต้องÆØถูกละเว้นขณะที่Åถูกแปลเป็น โดยปกติคุณจะแปลง“ å” เป็น“ aa”,“ ø” เป็น“ oe” และ“ æ” เป็น“ ae” วินาที (sb.Length == maxlen) ตัวแบ่ง; เป็นรถหากสัญญาณบน maxLenght-1 คือ "ß" (sb.Length == maxlen) จะไม่เป็นจริงมันจะดีกว่าแทนที่จะทดสอบ (sb.Length> = maxlen) ฉันถูกระงับที่คุณตัดตำแหน่งที่สุ่มและไม่ตัด "-" สิ่งนี้จะช่วยให้คุณประหยัดจากการลงท้ายด้วยคำที่ไม่ต้องการในท้ายที่สุด: ราวกับว่าคุณต้องตัด "เพื่อยืนยัน" หลังจาก "ครั้งสุดท้าย" "
Henrik Stenbæk

@DanH มันจะดีมากหากมีรหัสรุ่นจาวาสคริปต์
Freshblood

16

คุณจะต้องตั้งค่าเส้นทางที่กำหนดเองเพื่อชี้URLไปยังตัวควบคุมที่จะจัดการ เนื่องจากคุณใช้ Ruby on Rails นี่เป็นคำแนะนำในการใช้งานเอ็นจิ้นการเราติ้ง

ใน Ruby คุณจะต้องมีนิพจน์ทั่วไปอย่างที่คุณรู้อยู่แล้วและนี่คือนิพจน์ทั่วไปที่จะใช้:

def permalink_for(str)
    str.gsub(/[^\w\/]|[!\(\)\.]+/, ' ').strip.downcase.gsub(/\ +/, '-')
end

11

คุณยังสามารถใช้ฟังก์ชันJavaScriptนี้สำหรับการสร้างในรูปแบบของกระสุน (อันนี้ขึ้นอยู่กับ / คัดลอกมาจากDjango ):

function makeSlug(urlString, filter) {
    // Changes, e.g., "Petty theft" to "petty_theft".
    // Remove all these words from the string before URLifying

    if(filter) {
        removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
        "is", "in", "into", "like", "of", "off", "on", "onto", "per",
        "since", "than", "the", "this", "that", "to", "up", "via", "het", "de", "een", "en",
        "with"];
    }
    else {
        removelist = [];
    }
    s = urlString;
    r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
    s = s.replace(r, '');
    s = s.replace(/[^-\w\s]/g, ''); // Remove unneeded characters
    s = s.replace(/^\s+|\s+$/g, ''); // Trim leading/trailing spaces
    s = s.replace(/[-\s]+/g, '-'); // Convert spaces to hyphens
    s = s.toLowerCase(); // Convert to lowercase
    return s; // Trim to first num_chars characters
}

การเพิ่มบางส่วนของ Let's หรือ const จะดีมากเพราะนี่ไม่ใช่วานิลลา JS
Aditya Anand

8

สำหรับการวัดที่ดีนี่คือฟังก์ชั่น PHP ใน WordPress ที่ทำได้ ... ฉันคิดว่า WordPress เป็นหนึ่งในแพลตฟอร์มยอดนิยมที่ใช้ลิงค์แฟนซี

    ฟังก์ชัน sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ title);
            // รักษาอ็อกเท็ตที่ใช้ค่า Escape
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // ลบเครื่องหมายเปอร์เซ็นต์ที่ไม่ได้เป็นส่วนหนึ่งของ octet
            $ title = str_replace ('%', '', $ title);
            // คืนค่า octets
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (seem_utf8 ($ title)) {
                    if (function_exists ('mb_strtolower')) {
                            $ title = mb_strtolower ($ title, 'UTF-8');
                    }
                    $ title = utf8_uri_encode ($ title, 200);
            }
            $ title = strtolower ($ title);
            $ title = preg_replace ('/&.+?;/', '', $ title); // ฆ่าเอนทิตี้
            $ title = preg_replace ('/ [^% a-z0-9 _-] /', '', $ title);
            $ title = preg_replace ('/ \ s + /', '-', $ title);
            $ title = preg_replace ('| - + |', '-', $ title);
            $ title = trim ($ title, '-');
            ส่งคืนชื่อ $;
    }

ฟังก์ชั่นนี้รวมถึงฟังก์ชั่นการสนับสนุนบางอย่างสามารถพบได้ใน wp-include / formatting.php


6
นี่ไม่ใช่คำตอบเต็ม คุณไม่มีฟังก์ชันเช่น: remove_accents, seems_utf8...
Nikola Loncar

การทำ @ The How-Geek ให้ตอบคุณยังสามารถgit clone git://core.git.wordpress.org/และค้นหาwp-includes/formatting.phpไฟล์ได้
mickro

5

หากคุณใช้ Rails edge คุณสามารถพึ่งพาInflector.parametrizeนี่คือตัวอย่างจากเอกสาร:

  class Person
    def to_param
      "#{id}-#{name.parameterize}"
    end
  end

  @person = Person.find(1)
  # => #<Person id: 1, name: "Donald E. Knuth">

  <%= link_to(@person.name, person_path(@person)) %>
  # => <a href="https://stackoverflow.com/person/1-donald-e-knuth">Donald E. Knuth</a>

นอกจากนี้หากคุณต้องการจัดการตัวละครที่แปลกใหม่เช่นสำเนียง (éphémère) ใน Rails เวอร์ชันก่อนหน้าคุณสามารถใช้ส่วนผสมของPermalinkFuและDiacriticsFu :

DiacriticsFu::escape("éphémère")
=> "ephemere"

DiacriticsFu::escape("räksmörgås")
=> "raksmorgas"

5

ฉันไม่คุ้นเคยกับ Ruby on Rails แต่ต่อไปนี้คือโค้ด PHP ที่ยังไม่ได้ทดสอบ คุณสามารถแปลเป็น Ruby on Rails ได้อย่างรวดเร็วหากพบว่ามีประโยชน์

$sURL = "This is a title to convert to URL-format. It has 1 number in it!";
// To lower-case
$sURL = strtolower($sURL);

// Replace all non-word characters with spaces
$sURL = preg_replace("/\W+/", " ", $sURL);

// Remove trailing spaces (so we won't end with a separator)
$sURL = trim($sURL);

// Replace spaces with separators (hyphens)
$sURL = str_replace(" ", "-", $sURL);

echo $sURL;
// outputs: this-is-a-title-to-convert-to-url-format-it-has-1-number-in-it

ฉันหวังว่านี่จะช่วยได้.


4

ฉันไม่ค่อยเกี่ยวกับ Ruby หรือ Rails แต่ใน Perl นี่คือสิ่งที่ฉันจะทำ:

my $title = "How do you change a title to be part of the url like Stackoverflow?";

my $url = lc $title;   # Change to lower case and copy to URL.
$url =~ s/^\s+//g;     # Remove leading spaces.
$url =~ s/\s+$//g;     # Remove trailing spaces.
$url =~ s/\s+/\-/g;    # Change one or more spaces to single hyphen.
$url =~ s/[^\w\-]//g;  # Remove any non-word characters.

print "$title\n$url\n";

ฉันเพิ่งทำแบบทดสอบอย่างรวดเร็วและดูเหมือนว่าจะใช้งานได้ หวังว่ามันจะง่ายต่อการแปลเป็น Ruby


4

การใช้ T-SQL ดัดแปลงจากdbo.UrlEncode :

CREATE FUNCTION dbo.Slug(@string varchar(1024))
RETURNS varchar(3072)
AS
BEGIN
    DECLARE @count int, @c char(1), @i int, @slug varchar(3072)

    SET @string = replace(lower(ltrim(rtrim(@string))),' ','-')

    SET @count = Len(@string)
    SET @i = 1
    SET @slug = ''

    WHILE (@i <= @count)
    BEGIN
        SET @c = substring(@string, @i, 1)

        IF @c LIKE '[a-z0-9--]'
            SET @slug = @slug + @c

        SET @i = @i +1
    END

    RETURN @slug
END

4

ฉันรู้ว่ามันเป็นคำถามที่เก่ามาก แต่เนื่องจากเบราว์เซอร์ส่วนใหญ่สนับสนุน URL ของ Unicodeฉันจึงพบโซลูชันที่ยอดเยี่ยมในXRegexที่แปลงทุกอย่างยกเว้นตัวอักษร (ในทุกภาษาเป็น '-')

ที่สามารถทำได้ในหลายภาษาโปรแกรม

รูปแบบคือ\\p{^L}+จากนั้นคุณเพียงแค่ต้องใช้มันเพื่อแทนที่ตัวอักษรที่ไม่ใช่ทั้งหมดเป็น '-'

ตัวอย่างการทำงานใน node.js พร้อมโมดูลxregex

var text = 'This ! can @ have # several $ letters % from different languages such as עברית or Español';

var slugRegEx = XRegExp('((?!\\d)\\p{^L})+', 'g');

var slug = XRegExp.replace(text, slugRegEx, '-').toLowerCase();

console.log(slug) ==> "this-can-have-several-letters-from-different-languages-such-as-עברית-or-español"

3

สมมติว่าคลาสโมเดลของคุณมีแอตทริบิวต์หัวเรื่องคุณสามารถแทนที่เมธอด to_param ภายในโมเดลดังนี้:

def to_param
  title.downcase.gsub(/ /, '-')
end

ตอนนี้ Railscastมีรายละเอียดทั้งหมด คุณสามารถตรวจสอบให้แน่ใจว่าชื่อนั้นมีอักขระที่ถูกต้องเท่านั้นโดยใช้สิ่งนี้:

validates_format_of :title, :with => /^[a-z0-9-]+$/,
                    :message => 'can only contain letters, numbers and hyphens'

2

รหัสของ Brian ใน Ruby:

title.downcase.strip.gsub(/\ /, '-').gsub(/[^\w\-]/, '')

downcaseเปลี่ยนสตริงเป็นตัวพิมพ์เล็กstripลบช่องว่างนำหน้าและต่อท้ายการgsubโทรครั้งแรกg lobally ย่อย stitutes ช่องว่างด้วยขีดกลางและอันที่สองจะลบทุกอย่างที่ไม่ใช่ตัวอักษรหรือเส้นประ


2

มีปลั๊กอิน Ruby on Rails ขนาดเล็กชื่อPermalinkFuซึ่งทำสิ่งนี้ วิธีการหลบหนีไม่เปลี่ยนแปลงเป็นสตริงที่เหมาะสำหรับเป็นURL ลองดูรหัส; วิธีการนั้นค่อนข้างง่าย

ในการลบอักขระที่ไม่ใช่ASCIIจะใช้ iconv lib เพื่อแปลเป็น 'ascii // ละเว้น // translit' จาก 'utf-8' ช่องว่างนั้นจะเปลี่ยนเป็นเส้นประทุกอย่างย่อลง ฯลฯ


แม้ว่ามันจะทำงานได้อย่างสมบูรณ์แบบ แต่ฉันก็รู้สึกว่ามันไม่มีประสิทธิภาพ
WhyNotHugo

2

คุณสามารถใช้วิธีช่วยเหลือต่อไปนี้ มันสามารถแปลงอักขระ Unicode

public static string ConvertTextToSlug(string s)
{
    StringBuilder sb = new StringBuilder();

    bool wasHyphen = true;

    foreach (char c in s)
    {
        if (char.IsLetterOrDigit(c))
        {
            sb.Append(char.ToLower(c));
            wasHyphen = false;
        }
        else
            if (char.IsWhiteSpace(c) && !wasHyphen)
            {
                sb.Append('-');
                wasHyphen = true;
            }
    }

    // Avoid trailing hyphens
    if (wasHyphen && sb.Length > 0)
        sb.Length--;

    return sb.ToString().Replace("--","-");
}

2

นี่คือโค้ด Jeff ของฉัน (ช้าลง แต่สนุกในการเขียน):

public static string URLFriendly(string title)
{
    char? prevRead = null,
        prevWritten = null;

    var seq = 
        from c in title
        let norm = RemapInternationalCharToAscii(char.ToLowerInvariant(c).ToString())[0]
        let keep = char.IsLetterOrDigit(norm)
        where prevRead.HasValue || keep
        let replaced = keep ? norm
            :  prevWritten != '-' ? '-'
            :  (char?)null
        where replaced != null
        let s = replaced + (prevRead == null ? ""
            : norm == '#' && "cf".Contains(prevRead.Value) ? "sharp"
            : norm == '+' ? "plus"
            : "")
        let _ = prevRead = norm
        from written in s
        let __ = prevWritten = written
        select written;

    const int maxlen = 80;  
    return string.Concat(seq.Take(maxlen)).TrimEnd('-');
}

public static string RemapInternationalCharToAscii(string text)
{
    var seq = text.Normalize(NormalizationForm.FormD)
        .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark);

    return string.Concat(seq).Normalize(NormalizationForm.FormC);
}

สตริงทดสอบของฉัน:

" I love C#, F#, C++, and... Crème brûlée!!! They see me codin'... they hatin'... tryin' to catch me codin' dirty... "


2

แก้ปัญหา StackOverflowเป็นที่ดี แต่เบราว์เซอร์ที่ทันสมัย (ไม่รวม IE ตามปกติ) ตอนนี้จัดการการเข้ารหัส utf8 อย่าง:

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นฉันจึงอัพเกรดโซลูชั่นที่เสนอ:

public static string ToFriendlyUrl(string title, bool useUTF8Encoding = false)
{
    ...

        else if (c >= 128)
        {
            int prevlen = sb.Length;
            if (useUTF8Encoding )
            {
                sb.Append(HttpUtility.UrlEncode(c.ToString(CultureInfo.InvariantCulture),Encoding.UTF8));
            }
            else
            {
                sb.Append(RemapInternationalCharToAscii(c));
            }
    ...
}

รหัสเต็มบน Pastebin

แก้ไข: นี่คือรหัสสำหรับRemapInternationalCharToAsciiวิธีการ (ที่ไม่มีอยู่ใน pastebin)


ตามที่Wikipedia , Mozilla 1.4, Netscape 7.1, Opera 7.11 เป็นหนึ่งในแอพพลิเคชั่นแรกที่รองรับ IDNA ปลั๊กอินของเบราว์เซอร์พร้อมใช้งานสำหรับ Internet Explorer 6 เพื่อให้การสนับสนุน IDN API ของ Internet Explorer 7.0 และ Windows Vista ให้การสนับสนุนพื้นฐานสำหรับ IDN เสียงเหมือนการถอด UTF-8 ตัวอักษรเสียเวลา ยาวสด UTF-8 !!!
มูฮัมหมัดเรฮันซาอีด

1

ฉันชอบวิธีที่ทำได้โดยไม่ต้องใช้นิพจน์ทั่วไปดังนั้นฉันจึงย้ายไปที่ PHP ฉันเพิ่งเพิ่มฟังก์ชั่นที่เรียกว่าis_betweenตรวจสอบตัวละคร:

function is_between($val, $min, $max)
{
    $val = (int) $val; $min = (int) $min; $max = (int) $max;

    return ($val >= $min && $val <= $max);
}

function international_char_to_ascii($char)
{
    if (mb_strpos('àåáâäãåa', $char) !== false)
    {
        return 'a';
    }

    if (mb_strpos('èéêëe', $char) !== false)
    {
        return 'e';
    }

    if (mb_strpos('ìíîïi', $char) !== false)
    {
        return 'i';
    }

    if (mb_strpos('òóôõö', $char) !== false)
    {
        return 'o';
    }

    if (mb_strpos('ùúûüuu', $char) !== false)
    {
        return 'u';
    }

    if (mb_strpos('çccc', $char) !== false)
    {
        return 'c';
    }

    if (mb_strpos('zzž', $char) !== false)
    {
        return 'z';
    }

    if (mb_strpos('ssšs', $char) !== false)
    {
        return 's';
    }

    if (mb_strpos('ñn', $char) !== false)
    {
        return 'n';
    }

    if (mb_strpos('ýÿ', $char) !== false)
    {
        return 'y';
    }

    if (mb_strpos('gg', $char) !== false)
    {
        return 'g';
    }

    if (mb_strpos('r', $char) !== false)
    {
        return 'r';
    }

    if (mb_strpos('l', $char) !== false)
    {
        return 'l';
    }

    if (mb_strpos('d', $char) !== false)
    {
        return 'd';
    }

    if (mb_strpos('ß', $char) !== false)
    {
        return 'ss';
    }

    if (mb_strpos('Þ', $char) !== false)
    {
        return 'th';
    }

    if (mb_strpos('h', $char) !== false)
    {
        return 'h';
    }

    if (mb_strpos('j', $char) !== false)
    {
        return 'j';
    }
    return '';
}

function url_friendly_title($url_title)
{
    if (empty($url_title))
    {
        return '';
    }

    $url_title = mb_strtolower($url_title);

    $url_title_max_length   = 80;
    $url_title_length       = mb_strlen($url_title);
    $url_title_friendly     = '';
    $url_title_dash_added   = false;
    $url_title_char = '';

    for ($i = 0; $i < $url_title_length; $i++)
    {
        $url_title_char     = mb_substr($url_title, $i, 1);

        if (strlen($url_title_char) == 2)
        {
            $url_title_ascii    = ord($url_title_char[0]) * 256 + ord($url_title_char[1]) . "\r\n";
        }
        else
        {
            $url_title_ascii    = ord($url_title_char);
        }

        if (is_between($url_title_ascii, 97, 122) || is_between($url_title_ascii, 48, 57))
        {
            $url_title_friendly .= $url_title_char;

            $url_title_dash_added = false;
        }
        elseif(is_between($url_title_ascii, 65, 90))
        {
            $url_title_friendly .= chr(($url_title_ascii | 32));

            $url_title_dash_added = false;
        }
        elseif($url_title_ascii == 32 || $url_title_ascii == 44 || $url_title_ascii == 46 || $url_title_ascii == 47 || $url_title_ascii == 92 || $url_title_ascii == 45 || $url_title_ascii == 47 || $url_title_ascii == 95 || $url_title_ascii == 61)
        {
            if (!$url_title_dash_added && mb_strlen($url_title_friendly) > 0)
            {
                $url_title_friendly .= chr(45);

                $url_title_dash_added = true;
            }
        }
        else if ($url_title_ascii >= 128)
        {
            $url_title_previous_length = mb_strlen($url_title_friendly);

            $url_title_friendly .= international_char_to_ascii($url_title_char);

            if ($url_title_previous_length != mb_strlen($url_title_friendly))
            {
                $url_title_dash_added = false;
            }
        }

        if ($i == $url_title_max_length)
        {
            break;
        }
    }

    if ($url_title_dash_added)
    {
        return mb_substr($url_title_friendly, 0, -1);
    }
    else
    {
        return $url_title_friendly;
    }
}

1

ตอนนี้สิ่งที่เบราว์เซอร์ที่จับเข้ารหัส utf8 อย่างเพื่อให้คุณสามารถใช้WebUtility.UrlEncodeวิธีการของมันเช่นHttpUtility.UrlEncodeใช้โดย @giamin แต่อยู่นอกการทำงานของโปรแกรมประยุกต์บนเว็บ


1

ฉันย้ายรหัสไปยัง TypeScript สามารถปรับใช้กับ JavaScript ได้อย่างง่ายดาย

ฉันกำลังเพิ่ม.containsวิธีการในStringต้นแบบถ้าคุณกำหนดเป้าหมายเบราว์เซอร์ล่าสุดหรือ ES6 คุณสามารถใช้.includesแทน

if (!String.prototype.contains) {
    String.prototype.contains = function (check) {
        return this.indexOf(check, 0) !== -1;
    };
}

declare interface String {
    contains(check: string): boolean;
}

export function MakeUrlFriendly(title: string) {
            if (title == null || title == '')
                return '';

            const maxlen = 80;
            let len = title.length;
            let prevdash = false;
            let result = '';
            let c: string;
            let cc: number;
            let remapInternationalCharToAscii = function (c: string) {
                let s = c.toLowerCase();
                if ("àåáâäãåą".contains(s)) {
                    return "a";
                }
                else if ("èéêëę".contains(s)) {
                    return "e";
                }
                else if ("ìíîïı".contains(s)) {
                    return "i";
                }
                else if ("òóôõöøőð".contains(s)) {
                    return "o";
                }
                else if ("ùúûüŭů".contains(s)) {
                    return "u";
                }
                else if ("çćčĉ".contains(s)) {
                    return "c";
                }
                else if ("żźž".contains(s)) {
                    return "z";
                }
                else if ("śşšŝ".contains(s)) {
                    return "s";
                }
                else if ("ñń".contains(s)) {
                    return "n";
                }
                else if ("ýÿ".contains(s)) {
                    return "y";
                }
                else if ("ğĝ".contains(s)) {
                    return "g";
                }
                else if (c == 'ř') {
                    return "r";
                }
                else if (c == 'ł') {
                    return "l";
                }
                else if (c == 'đ') {
                    return "d";
                }
                else if (c == 'ß') {
                    return "ss";
                }
                else if (c == 'Þ') {
                    return "th";
                }
                else if (c == 'ĥ') {
                    return "h";
                }
                else if (c == 'ĵ') {
                    return "j";
                }
                else {
                    return "";
                }
            };

            for (let i = 0; i < len; i++) {
                c = title[i];
                cc = c.charCodeAt(0);

                if ((cc >= 97 /* a */ && cc <= 122 /* z */) || (cc >= 48 /* 0 */ && cc <= 57 /* 9 */)) {
                    result += c;
                    prevdash = false;
                }
                else if ((cc >= 65 && cc <= 90 /* A - Z */)) {
                    result += c.toLowerCase();
                    prevdash = false;
                }
                else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') {
                    if (!prevdash && result.length > 0) {
                        result += '-';
                        prevdash = true;
                    }
                }
                else if (cc >= 128) {
                    let prevlen = result.length;
                    result += remapInternationalCharToAscii(c);
                    if (prevlen != result.length) prevdash = false;
                }
                if (i == maxlen) break;
            }

            if (prevdash)
                return result.substring(0, result.length - 1);
            else
                return result;
        }

0

ไม่ไม่ไม่. คุณผิดไปมาก ยกเว้นสิ่งที่กำกับ-Fu, คุณจะได้รับมี แต่สิ่งที่เกี่ยวกับตัวละครเอเชีย (ความละอายในการพัฒนาทับทิมไม่พิจารณาพวกเขาnihonjinพี่น้อง)

Firefox และ Safari ทั้งสองแสดงอักขระที่ไม่ใช่ ASCII ในURLและตรงไปตรงมาพวกเขาดูดี มันเป็นสิ่งที่ดีที่จะเชื่อมโยงการสนับสนุนเช่น ' http://somewhere.com/news/read/お前たちはアホじゃないかい'

ดังนั้นนี่คือรหัส PHP ที่จะทำ แต่ฉันเพิ่งเขียนมันและไม่ได้ทดสอบความเครียด

<?php
    function slug($str)
    {
        $args = func_get_args();
        array_filter($args);  //remove blanks
        $slug = mb_strtolower(implode('-', $args));

        $real_slug = '';
        $hyphen = '';
        foreach(SU::mb_str_split($slug) as $c)
        {
            if (strlen($c) > 1 && mb_strlen($c)===1)
            {
                $real_slug .= $hyphen . $c;
                $hyphen = '';
            }
            else
            {
                switch($c)
                {
                    case '&':
                        $hyphen = $real_slug ? '-and-' : '';
                        break;
                    case 'a':
                    case 'b':
                    case 'c':
                    case 'd':
                    case 'e':
                    case 'f':
                    case 'g':
                    case 'h':
                    case 'i':
                    case 'j':
                    case 'k':
                    case 'l':
                    case 'm':
                    case 'n':
                    case 'o':
                    case 'p':
                    case 'q':
                    case 'r':
                    case 's':
                    case 't':
                    case 'u':
                    case 'v':
                    case 'w':
                    case 'x':
                    case 'y':
                    case 'z':

                    case 'A':
                    case 'B':
                    case 'C':
                    case 'D':
                    case 'E':
                    case 'F':
                    case 'G':
                    case 'H':
                    case 'I':
                    case 'J':
                    case 'K':
                    case 'L':
                    case 'M':
                    case 'N':
                    case 'O':
                    case 'P':
                    case 'Q':
                    case 'R':
                    case 'S':
                    case 'T':
                    case 'U':
                    case 'V':
                    case 'W':
                    case 'X':
                    case 'Y':
                    case 'Z':

                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        $real_slug .= $hyphen . $c;
                        $hyphen = '';
                        break;

                    default:
                       $hyphen = $hyphen ? $hyphen : ($real_slug ? '-' : '');
                }
            }
        }
        return $real_slug;
    }

ตัวอย่าง:

$str = "~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 コリン ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 トーマス ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 アーノルド ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04";
echo slug($str);

เอาท์พุท: コリン -and- ーーマス -and--ーノノルド

'- และ -' เป็นเพราะ & s ได้รับการเปลี่ยนเป็น '- และ -'


4
ฉันไม่รู้จะพูดอะไรเกี่ยวกับข้อมูลส่วนนี้จริงๆ
sjas

3
นั่นเป็นตัวอย่างที่ดีมากเมื่อไม่ใช้คำสั่ง switch case
NickG
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.