Яким чином переповнення стека генерує SEO-дружні URL-адреси?


253

Що є гарним повним регулярним виразом чи іншим процесом, який би прийняв назву:

Як змінити заголовок, щоб він був частиною URL-адреси, наприклад, Переповнення стека?

і перетворити його на

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

що використовується в зручних для SEO URL-адресах під час переповнення стека?

Середовище розробки, яке я використовую, це Ruby on Rails , але якщо є якісь інші платформенні рішення (.NET, PHP, Django ), я також хотів би їх побачити.

Я впевнений, що я (або інший читач) зіткнуся з тією ж проблемою на іншій платформі вниз.

Я використовую власні маршрути, і я головним чином хочу знати, як змінити рядок, щоб всі спеціальні символи були видалені, це все малі літери, і все пробіли замінено.


Що з кумедними персонажами? Що ти будеш робити з цим? Умлаутс? Знаки пунктуації? Це потрібно врахувати. В основному, я б застосував підхід до білого списку, на відміну від підходів до чорного списку, описаних вище: Опишіть, які символи ви дозволите, які символи ви перетворите (на що?), А потім змініть решту на щось значуще ("") . Сумніваюсь, ви можете це зробити за один регулярний вираз ... Чому б просто не пропустити символи?
Дарен Томас

1
Слід перенести на мета ; оскільки питання і відповіді конкретно стосуються впровадження SO, а прийнята відповідь - від @JeffAtwood.
casperOne

19
@casperOne Ви вважаєте, що Джеффу не дозволена якась неметативна репутація? Питання стосується "як можна зробити щось подібне", а не конкретно "як це робиться тут".
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();
}

Щоб побачити попередню версію коду, яку замінили (але вона функціонально еквівалентна і на 5 разів швидша), перегляньте історію редагування цієї публікації (натисніть посилання дати).

Також RemapInternationalCharToAsciiвихідний код методу можна знайти тут .


24
Було б непогано з версією, яка не просто скидає наголошені символи, як åäö, а замість цього деацентує їх на aao ... ^^
Oskar Duveborn

22
@oskar заглушка цієї RemapInternationalCharToAscii()функції є meta.stackexchange.com/questions/7435/…
Джефф Етвуд

3
Це чудово. Єдина зміна, яку я зробив поки що, - це змінити "if (i == maxlen) break;" стати "якщо (sb.Length == maxlen) перерва;" на всякий випадок, якщо в рядку, який я проходжу, є багато недійсних символів.
Том Чентлер,

4
Невелика оптимізація: if (prevdash) sb.Length -= 1; return sb.ToString();замість останнього ifтвердження.
Марк Херд

8
@Dommer sb.Length == maxlen break;баггі, якщо знак на maxLenght-1 "ß", він перетворюється на "ss" sb.Length == maxleneніколи не буде правдою, краще замість цього тестувати (sb.Length > = maxlen).
Генрік Стенбек

32

Ось моя версія коду Джеффа. Я вніс такі зміни:

  • Дефіси додавались таким чином, що можна було додати, а потім потрібно видалити, оскільки це був останній символ у рядку. Тобто, ми ніколи не хочемо «мій-слизький». Це означає додатковий розподіл рядка, щоб видалити його в цьому крайньому регістрі. Я працював над цим шляхом затримки з дефісом. Якщо ви порівнюєте мій код з логікою Джеффа, це легко дотримуватися.
  • Його підхід ґрунтується на суто пошуку і пропустив багато персонажів, які я знайшов у прикладах під час дослідження Stack Overflow. Щоб протистояти цьому, я спочатку формую пропуск для нормалізації (зіставлення AKA, згадане в питанні щодо переповнення Meta Stack Non символи US-ASCII, скинуті з повної URL-адреси (профілю) ), а потім ігнорую будь-які символи за межами прийнятних діапазонів. Це працює більшу частину часу ...
  • ... Бо коли це не так, мені також довелося додати таблицю пошуку. Як згадувалося вище, деякі символи не нормалізуються при низькому значенні ASCII при нормалізації. Замість того, щоб опустити ці дані, я отримав вручну список винятків, який безперечно повний дірок, але це краще, ніж нічого. Код нормалізації був надихнутий чудовим постом Джона Ханна у питанні 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 повторень щодо коду Джеффа показав, що він приблизно однаковий.
Том Чантлер

1
Дякую, відгукнувся у своєму блозі та виправив код там і вище. Також дякую за тестування коду. Для зацікавлених це було нарівні з Джеффом.
DanH

2
Здається, що з Slug.Create () є деякі проблеми: Верхні версії ÆØÅ не належним чином перетворені ÆØ ігнорується, коли Å переводиться на a. Зазвичай ви перетворите "å" в "aa", "ø" в "oe" і "æ" в "ae". Другий (sb.Length == maxlen) перерва; є баггі, якщо знак на maxLenght-1 є "ß" (sb.Length == maxlen) ніколи не буде правдою, краще замість цього перевірити на (sb.Length> = maxlen). Мене пригнічують, що ви вирізаєте будь-яку випадкову позицію, а не вирізаєте останнє "-", це врятує вас від того, щоб у кінцевому підсумку не було потрібного слова: як би вам довелося вирізати "стверджувати" після останнього "s "
Генрік Стенбек

@DanH було б чудово мати версію коду javascript.
Пріснокровна

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
}

Додавання трохи давайте або const було б чудово, оскільки це не ванільний JS.
Адітя Ананд

8

На жаль, ось функція PHP в WordPress, яка це робить ... Я думаю, що WordPress - одна з найпопулярніших платформ, яка використовує химерні посилання.

    функція sanitize_title_with_dashes ($ назва) {
            $ title = strip_tags ($ title);
            // Зберегти втечені октети.
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // Видаліть знаки відсотків, які не входять до складу октету.
            $ title = str_replace ('%', '', $ title);
            // Відновити октети.
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = delete_accents ($ title);
            якщо (здається_utf8 ($ назва)) {
                    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 = обрізка ($ title, '-');
            повернути $ title;
    }

Цю функцію, а також деякі підтримуючі функції можна знайти в wp-include / formatting.php.


6
Це не повна відповідь. У вас не вистачає функцій , таких як: remove_accents, seems_utf8...
Нікола Лончар

для завершення @The How-To 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

Я не дуже про Рубі або Рейки, але в 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";

Я просто зробив швидкий тест і, здається, працює. Сподіваємось, це відносно легко перекласти на Рубі.


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

Код Брайана в Рубі:

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

downcaseперетворює рядок у нижній регістр, stripвидаляє початкові і кінцеві пробіли, перший gsubвиклик г lobally суб stitutes простору з тире, а другий видаляє всі , що не є буквою або тире.


2

Є невеликий плагін Ruby on Rails під назвою PermalinkFu , який робить це. Метод escape забезпечує перетворення в рядок, який підходить для URL-адреси . Погляньте на код; цей метод досить простий.

Для видалення символів, що не належать до ASCII, вона використовує liv iconv для перекладу на "ascii // ignore // 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

Ось моя (повільніше, але весело писати) версія коду Джеффа:

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

Повний код на пастебіні

Edit: Ось код для RemapInternationalCharToAsciiметоду (який відсутній в Pastebin).


За даними Вікіпедії , Mozilla 1.4, Netscape 7.1, Opera 7.11 були одними з перших додатків для підтримки IDNA. Для забезпечення підтримки IDN для Internet Explorer 6 доступний плагін для браузера. URL-адреси 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

Я переніс код у 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

Ні-ні-ні. Ви всі так сильно помиляєтесь. За винятком матеріалів діакритики-фу, ви туди потрапляєте, а як же бути з азіатськими персонажами (ганьба розробникам Ruby за те, що вони не вважають своїх братів nihonjin ).

Firefox та Safari в URL-адресі відображають символи, що не належать до ASCII , і, відверто кажучи, вони виглядають чудово. Приємно підтримувати посилання типу " 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- ア ー ノ ド

'-І-' є тому, що & 'змінюється на' -і- '.


4
Я справді не знаю, що сказати на цю інформацію.
sjas

3
Це дійсно хороший приклад того, коли НЕ використовувати оператор випадку перемикача.
NickG
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.