Как Stack Overflow генерирует свои 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 ), я бы тоже хотел их увидеть.

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

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

wusher
источник
А как насчет забавных персонажей? Что ты будешь делать с этими? Умляуты? Пунктуация? Это необходимо учитывать. По сути, я бы использовал подход белого списка, в отличие от описанных выше подходов черного списка: опишите, какие символы вы разрешите, какие символы вы будете преобразовывать (во что?), А затем измените остальные на что-то значащее ("") , Я сомневаюсь, что вы можете сделать это в одном регулярном выражении ... Почему бы просто не перебрать персонажей?
Дарен Томас
1
Должны быть перенесены в мета ; поскольку вопрос и ответ конкретно касаются реализации SO, и принятый ответ от @JeffAtwood.
casperOne
19
@casperOne Как ты думаешь, Джеффу не позволено иметь нематетическую репутацию? Вопрос в том, «как можно сделать что-то подобное», а не «как это здесь делается».
Пауло Эберманн
@ 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 ... ^^
Оскар Дюверборн
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

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

  • Дефисы были добавлены таким образом, чтобы их можно было добавить, а затем их нужно удалить, поскольку это был последний символ в строке. То есть мы никогда не хотим «мой слизень». Это означает дополнительное выделение строки для ее удаления в этом крайнем случае. Я работал над этим с помощью переноса слов. Если вы сравните мой код с Джеффом, логика для этого будет легко следовать.
  • Его подход основан исключительно на поиске и пропустил много символов, которые я нашел в примерах при исследовании переполнения стека. Чтобы противостоять этому, я сначала выполняю этап нормализации (сопоставление AKA, упомянутое в вопросе о переполнении стека Meta Stack. Номера символов US-ASCII отброшены из полного URL-адреса (профиля) ), а затем игнорирую любые символы вне допустимых диапазонов. Это работает большую часть времени ...
  • ... Ибо, когда это не так, мне также пришлось добавить таблицу поиска. Как упоминалось выше, некоторые символы не отображаются на низкое значение ASCII при нормализации. Вместо того, чтобы отбрасывать их, у меня есть ручной список исключений, который, несомненно, полон дыр, но это лучше, чем ничего. Код нормализации был вдохновлен замечательным постом Джона Ханны в вопросе переполнения стека. Как удалить акценты в строке? ,
  • Преобразование регистра теперь также необязательно.

    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 схема немного умнее Переполнение стека, я получил расширенную версию этого на моем блоге .

Danh
источник
4
+1 Это здорово, Дэн. Я также добавил комментарий в ваш блог о возможном изменении if (i == maxlen) break;на if (sb.Length == maxlen) break;него, чтобы, если вы передадите строку с большим количеством пробелов / недопустимых символов, вы все равно можете получить фрагмент с желаемой длиной, тогда как код в его нынешнем виде может закончиться массово обрезая его (например, рассмотрим случай, когда вы начинаете с 80 пробелов ...). Примерно 10 000 000 итераций в сравнении с кодом Джеффа показали, что скорость примерно одинакова.
Том Чантлер
1
Спасибо, ответил на мой блог и исправил код там и выше. Также спасибо за сравнительный анализ кода. Для интересующихся это было наравне с Джеффом.
ДанХ
2
Кажется, что есть некоторые проблемы с Slug.Create (): прописные версии ÆØÅ не конвертируются должным образом, getsØ игнорируется, пока Å переводится в a. Обычно вы конвертируете «å» в «aa», «ø» в «oe» и «æ» в «ae». Второй (sb.Length == maxlen) перерыв; глючит, если знак maxLenght-1 равен «ß» (sb.Length == maxlen) никогда не будет истинным, лучше вместо этого проверить (sb.Length> = maxlen). Я подавлен тем, что вы сокращаете любую случайную позицию, а не сокращаете последнюю «-», это избавит вас от необходимости заканчивать нежелательным словом в конце: как если бы вам пришлось сокращать «утверждать» после последней "
Хенрик Стенбек
@DanH было бы здорово иметь версию кода на javascript.
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
}
fijter
источник
Было бы замечательно добавить немного let или const, поскольку это не vanilla JS.
Адитья Ананд
8

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

    function sanitize_title_with_dashes ($ title) {
            $ 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 = remove_accents ($ title);
            if (кажется_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, '-');
            вернуть $ title;
    }

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

How-To Geek
источник
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

Я не очень разбираюсь в 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"
Rotem
источник
3

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

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

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

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

Сёрен Куклау
источник
2

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

Для удаления не- ASCII символов он использует иконку lib для перевода в 'ascii // ignore // translit' из 'utf-8'. Затем пробелы превращаются в тире, все в нижнем регистре и т. Д.

Lau
источник
Хотя это работает отлично, я почему-то чувствую, что это не очень эффективно.
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));
            }
    ...
}

Полный код на Pastebin

Изменить: вот код для RemapInternationalCharToAsciiметода (отсутствует в pastebin).

giammin
источник
Согласно Википедии , Mozilla 1.4, Netscape 7.1, Opera 7.11 были одними из первых приложений, поддерживающих IDNA. Для Internet Explorer 6 доступен плагин для браузера, обеспечивающий поддержку IDN. Internet Explorer 7.0 и API-интерфейсы URL в 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, но он работает вне веб-приложения.

ikourfaln
источник
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 отображают не-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-' потому что & 's изменен на' -and- '.

Питер Мортенсен
источник
4
Я действительно не знаю, что сказать об этой части информации.
Sjas
3
Это действительно хороший пример того, когда НЕ использовать оператор регистра переключателя.
Ник