Как Stack Overflow генерирует оптимизированные для SEO URL-адреса?

StackOverflow https://stackoverflow.com/questions/25259

Вопрос

Что такое хороший комплект регулярное выражение или какой-либо другой процесс, который будет иметь заголовок:

Как изменить заголовок, чтобы он стал частью URL-адреса, например Stack Overflow?

и превратить его в

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

который используется в оптимизированных для SEO URL-адресах в Stack Overflow?

Среда разработки, которую я использую, Рубин на рельсах, но если есть какие-то другие решения для конкретной платформы (.NET, PHP, Джанго), мне бы тоже хотелось их увидеть.

Я уверен, что я (или другой читатель) столкнусь с той же проблемой на другой платформе в будущем.

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

Это было полезно?

Решение

Вот как мы это делаем.Обратите внимание, что граничных условий, вероятно, больше, чем вы думаете на первый взгляд.

Это вторая версия, развернутая для повышения производительности в 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 исходный код метода можно найти здесь.

Другие советы

Вот моя версия кода Джеффа.Я внес следующие изменения:

  • Дефисы были добавлены таким образом, что их можно было добавить, а затем удалить, поскольку это был последний символ в строке.То есть нам никогда не нужен «мой-слизень-».Это означает дополнительное выделение строки для ее удаления в этом крайнем случае.Я обошёл эту проблему, расставив переносы с задержкой.Если вы сравните мой код с кодом Джеффа, логику этого легко понять.
  • Его подход основан исключительно на поиске, и он упустил много символов, которые я нашел в примерах, исследуя Stack Overflow.Чтобы противостоять этому, я сначала выполняю проход нормализации (сопоставление AKA, упомянутое в вопросе о переполнении стека Meta). Символы, отличные от 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
        /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
        /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
        /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string
        /// </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;
        }
    }
    

Более подробная информация о модульных тестах и ​​объяснение того, почему Фейсбук's URL-адрес схема немного умнее, чем Stack Overflows, у меня есть расширенная версия этого в моем блоге.

Вы захотите настроить собственный маршрут, чтобы указать URL-адрес контроллеру, который будет его обрабатывать.Поскольку вы используете Ruby on Rails, вот введение в использовании их механизма маршрутизации.

В Ruby вам понадобится регулярное выражение, которое вы уже знаете, и вот его регулярное выражение:

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

Вы также можете использовать это JavaScript функция для генерации пулов в форме (она основана на/скопирована из Джанго):

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
}

Для наглядности вот функция PHP в WordPress, которая делает это...Я думаю, что WordPress — одна из наиболее популярных платформ, использующих необычные ссылки.

    function sanitize_title_with_dashes($title) {
            $title = strip_tags($title);
            // Preserve escaped octets.
            $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
            // Remove percent signs that are not part of an octet.
            $title = str_replace('%', '', $title);
            // Restore octets.
            $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title);
            $title = remove_accents($title);
            if (seems_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); // kill entities
            $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
            $title = preg_replace('/\s+/', '-', $title);
            $title = preg_replace('|-+|', '-', $title);
            $title = trim($title, '-');
            return $title;
    }

Эту функцию, а также некоторые вспомогательные функции можно найти в wp-includes/formatting.php.

Если вы используете Rails Edge, вы можете положиться на Инфлектор.параметризировать - вот пример из документации:

  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="/person/1-donald-e-knuth">Donald E. Knuth</a>

Также, если вам нужно обрабатывать более экзотические символы, такие как акценты (эфемер) в предыдущей версии Rails, вы можете использовать смесь Постоянная ссылкаFu и Диакритические знакиFu:

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

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

Я не знаком с 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

Надеюсь, это поможет.

Я не особо разбираюсь в 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.

Реализация 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

Предполагая, что ваш класс модели имеет атрибут title, вы можете просто переопределить метод 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'

Я знаю, что это очень старый вопрос, но поскольку большинство браузеров сейчас поддержка URL-адресов в Юникоде Я нашел отличное решение в XRegex который преобразует все, кроме букв (на всех языках в «-»).

Это можно сделать на нескольких языках программирования.

Шаблон \\p{^L}+ и тогда вам просто нужно использовать его, чтобы заменить все небуквы на «-».

Рабочий пример в node.js с регулярное выражение модуль.

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"

Код Брайана на Ruby:

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

downcase переводит строку в нижний регистр, strip удаляет начальные и конечные пробелы, первый gsub вызов глокально субзаменяет пробелы тире, а второй удаляет все, что не является буквой или тире.

Существует небольшой плагин Ruby on Rails под названием Постоянная ссылкаFu, это делает это.А метод побега выполняет преобразование в строку, подходящую для URL-адрес.Посмотрите на код;этот метод довольно прост.

Чтобы удалить не-ASCII символов, он использует библиотеку iconv для перевода в «ascii//ignore//translit» из «utf-8».Пробелы затем превращаются в тире, все понижается и т. д.

Вы можете использовать следующий вспомогательный метод.Он может конвертировать символы Юникода.

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

Вот моя (медленная, но интересная) версия кода Джеффа:

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... "

А решение для переполнения стека это здорово, но современный браузер (за исключением IE, как обычно) теперь прекрасно обрабатывает кодировку utf8:

enter image description here

Поэтому я обновил предложенное решение:

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

Мне понравилось, как это делается без использования обычные выражения, поэтому я портировал его на 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;
    }
}

Теперь все браузеры прекрасно обрабатывают кодировку utf8, поэтому вы можете использовать WebUtility.UrlEncode Метод, он такой HttpUtility.UrlEncode используется @giamin, но работает вне веб-приложения.

Нет нет нет.Вы все очень сильно ошибаетесь.Если не считать диакритических знаков, вы это поняли, но как насчет азиатских символов (позор разработчикам Ruby, которые не учли их нихонджин братья).

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

Выходы:コリン-и-トーマス-и-アーノルド

«-и-» означает, что & заменяется на «-и-».

Я портировал код на 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;
        }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top