Как Stack Overflow генерирует свои SEO-дружественные URL-адреса?
Что такое хорошее полное регулярное выражение или какой-то другой процесс, который бы взял название:
Как изменить заголовок, чтобы он стал частью URL, например переполнение стека?
и превратить его в
how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow
что используется в SEO-дружественных URL-адресах при переполнении стека?
Среда разработки, которую я использую, - это Ruby on Rails, но если есть какие-то другие решения для конкретной платформы (.NET, PHP, Django), я бы тоже хотел их увидеть.
Я уверен, что я (или другой читатель) столкнусь с той же проблемой на другой платформе.
Я использую пользовательские маршруты, и я в основном хочу знать, как изменить строку, чтобы все специальные символы были удалены, все это в нижнем регистре и все пробелы заменены.
20 ответов
Вот как мы это делаем. Обратите внимание, что, вероятно, существует больше граничных условий, чем вы думаете на первый взгляд.
Это вторая версия, развернутая для увеличения производительности в 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
Исходный код метода можно найти здесь.
Вот моя версия кода Джеффа. Я сделал следующие изменения:
- Дефисы были добавлены таким образом, чтобы их можно было добавить, а затем их нужно удалить, поскольку это был последний символ в строке. То есть мы никогда не хотим "мой слизень". Это означает дополнительное выделение строки для ее удаления в этом крайнем случае. Я работал над этим с помощью переноса слов. Если вы сравните мой код с Джеффом, логика для этого будет легко следовать.
- Его подход основан исключительно на поиске и пропустил много символов, которые я нашел в примерах при исследовании переполнения стека. Чтобы противостоять этому, я сначала выполняю этап нормализации (сопоставление AKA, упомянутое в вопросе "Переполнение стека мета". Номера символов 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 /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696 /// https://stackru.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486 /// https://stackru.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; } }
Для получения более подробной информации, модульных тестов и объяснения того, почему схема URL в Facebook немного умнее, чем Stack Overflows, я получил расширенную версию этого в своем блоге.
Вы захотите настроить собственный маршрут, чтобы указать URL-адрес контроллера, который будет его обрабатывать. Поскольку вы используете Ruby on Rails, вот введение в использование их механизма маршрутизации.
В Ruby вам понадобится регулярное выражение, как вы уже знаете, и вот регулярное выражение для использования:
def permalink_for(str)
str.gsub(/[^\w\/]|[!\(\)\.]+/, ' ').strip.downcase.gsub(/\ +/, '-')
end
Вы также можете использовать эту функцию 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
}
Для примера, вот функция 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.
Я не знаком с 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
Надеюсь, это поможет.
Если вы используете 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="/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"
Реализация 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
Я не очень разбираюсь в Ruby или Rails, но в Perl я бы так и сделал:
my $title = "How do you change a title to be part of the url like Stackru?";
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.
Я знаю, что это очень старый вопрос, но так как большинство браузеров теперь поддерживают 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"
Предполагая, что ваш класс модели имеет атрибут 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'
А как насчет забавных персонажей? Что ты будешь делать с этими? Умляуты? Пунктуация? Это необходимо учитывать. По сути, я бы использовал подход белого списка, в отличие от описанных выше подходов черного списка: опишите, какие символы вы разрешите, какие символы вы будете преобразовывать (во что?), А затем измените остальные на что-то значащее (""), Я сомневаюсь, что вы можете сделать это в одном регулярном выражении... Почему бы просто не перебрать персонажей?
Вы можете использовать следующий вспомогательный метод. Он может конвертировать символы 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("--","-");
}
Для этого существует небольшой плагин Ruby on Rails, который называется PermalinkFu. Метод escape выполняет преобразование в строку, подходящую для URL. Посмотрите на код; этот метод довольно прост.
Для удаления не- ASCII символов он использует иконку lib для перевода в 'ascii//ignore//translit' из 'utf-8'. Затем пробелы превращаются в тире, все в нижнем регистре и т. Д.
Код Брайана в Ruby:
title.downcase.strip.gsub(/\ /, '-').gsub(/[^\w\-]/, '')
downcase
превращает строку в нижний регистр, strip
удаляет начальные и конечные пробелы, первый gsub
call g lobally заменяет пробелы черточками, а вторая удаляет все, что не является буквой или тире.
Вот моя (более медленная, но забавная написание) версия кода Джеффа:
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... "
Решение stackru отлично, но современный браузер (исключая 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));
}
...
}
Изменить: вот код для RemapInternationalCharToAscii
метод (это отсутствует в пастбине).
Мне понравилось, как это делается без использования регулярных выражений, поэтому я перенес его на 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;
}
}
Я портировал код на 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;
}
Теперь все браузеры прекрасно справляются с кодировкой utf8, поэтому вы можете использовать метод WebUtility.UrlEncode, похожий на HttpUtility.UrlEncode, используемый @giamin, но он работает вне веб-приложения.
Нет нет нет. Вы все так сильно ошибаетесь. За исключением диакритического фу, у вас все получится, но как насчет азиатских персонажей (позор разработчиков 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 リン-и-トーマス-и-アーノルド
'-And-' потому что &'s изменен на' -and- '.
Перепишите код Джеффа, чтобы он был более лаконичным
public static string RemapInternationalCharToAscii(char c)
{
var s = c.ToString().ToLowerInvariant();
var mappings = new Dictionary<string, string>
{
{ "a", "àåáâäãåą" },
{ "c", "çćčĉ" },
{ "d", "đ" },
{ "e", "èéêëę" },
{ "g", "ğĝ" },
{ "h", "ĥ" },
{ "i", "ìíîïı" },
{ "j", "ĵ" },
{ "l", "ł" },
{ "n", "ñń" },
{ "o", "òóôõöøőð" },
{ "r", "ř" },
{ "s", "śşšŝ" },
{ "ss", "ß" },
{ "th", "Þ" },
{ "u", "ùúûüŭů" },
{ "y", "ýÿ" },
{ "z", "żźž" }
};
foreach(var mapping in mappings)
{
if (mapping.Value.Contains(s))
return mapping.Key;
}
return string.Empty;
}