¿Cómo genera Stack Overflow sus URL amigables para SEO?

¿Qué es una buena expresión regular completa o algún otro proceso que tomaría el título?

¿Cómo se cambia un título para formar parte de la URL como Stack Overflow?

y convertirlo en

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

que se utiliza en las URL amigables para SEO en Stack Overflow?

El entorno de desarrollo que estoy utilizando es Ruby on Rails , pero si hay otras soluciones específicas de la plataforma (.NET, PHP, Django ), me encantaría verlas también.

Estoy seguro de que yo (u otro lector) se encontrará con el mismo problema en una plataforma diferente al final de la línea.

Estoy usando rutas personalizadas, y principalmente quiero saber cómo modificar la cadena para que se eliminen todos los caracteres especiales, todo está en minúscula y se reemplazan todos los espacios en blanco.

    Así es como lo hacemos. Tenga en cuenta que probablemente haya más condiciones de borde de lo que cree a primera vista.

    Esta es la segunda versión, desenrollada para 5 veces más rendimiento (y sí, lo comparé). Pensé que lo optimizaría porque esta función se puede llamar cientos de veces por página.

     ///  /// Produces optional, URL-friendly version of a title, "like-this-one". /// hand-tuned for speed, reflects performance refactoring contributed /// by John Gietzen (user otac0n) ///  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(); } 

    Para ver la versión anterior del código reemplazado (pero es funcionalmente equivalente a, y 5 veces más rápido), ver el historial de revisión de esta publicación (haga clic en el enlace de la fecha).

    Además, el código fuente del método RemapInternationalCharToAscii se puede encontrar aquí .

    Aquí está mi versión del código de Jeff. Hice los siguientes cambios:

    • Los guiones se agregaron de tal manera que se podría agregar uno, y luego se debe eliminar ya que era el último carácter de la cadena. Es decir, nunca queremos “my-slug-“. Esto significa una asignación de cadena adicional para eliminarlo en este caso extremo. He solucionado esto mediante el uso de guiones de retraso. Si comparas mi código con el de Jeff, la lógica para esto es fácil de seguir.
    • Su enfoque es puramente de búsqueda y se perdió muchos personajes que encontré en ejemplos mientras investigaba en Stack Overflow. Para contrarrestar esto, primero realizo un pase de normalización (la intercalación de AKA mencionada en la Pregunta de desbordamiento de Meta Stack Caracteres que no son US-ASCII abandonados de la URL completa (de perfil) ) y luego ignoro cualquier carácter fuera de los rangos aceptables. Esto funciona la mayor parte del tiempo…
    • … Porque cuando no es así, también he tenido que agregar una tabla de búsqueda. Como se mencionó anteriormente, algunos caracteres no se asignan a un valor ASCII bajo cuando se normalizan. En lugar de soltar estos, tengo una lista manual de excepciones que, sin duda, está llena de agujeros, pero es mejor que nada. El código de normalización se inspiró en la gran publicación de Jon Hanna en Stack Overflow question. ¿Cómo puedo eliminar acentos en una cadena? .
    • La conversión de casos ahora también es opcional.

       public static class Slug { public static string Create(bool toLower, params string[] values) { return Create(toLower, String.Join("-", values)); } ///  /// 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 ///  ///  ///  ///  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; } } 

    Para obtener más detalles, las pruebas de la unidad y una explicación de por qué el esquema de URL de Facebook es un poco más inteligente que Stack Overflows, tengo una versión ampliada de esto en mi blog .

    Deberá configurar una ruta personalizada para señalar la URL al controlador que la manejará. Dado que está utilizando Ruby on Rails, aquí hay una introducción al uso de su motor de enrutamiento.

    En Ruby, necesitarás una expresión regular como la que ya conoces y aquí está la expresión regular para usar:

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

    También puede usar esta función de JavaScript para la generación en forma de slug (esta está basada en / copiada de Django ):

     function makeSlug(urlString, filter) { // Changes, eg, "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 } 

    Para una buena medida, aquí está la función de PHP en WordPress que lo hace … Creo que WordPress es una de las plataformas más populares que utiliza enlaces de fantasía.

         function sanitize_title_with_dashes ($ title) {
                 $ title = strip_tags ($ title);
                 // Conservar octetos escapados.
                 $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
                 // Eliminar signos de porcentaje que no son parte de un octeto.
                 $ title = str_replace ('%', '', $ title);
                 // Restaurar octetos.
                 $ 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);  // matar entidades
                 $ title = preg_replace ('/ [^% a-z0-9 _-] /', '', $ title);
                 $ title = preg_replace ('/ \ s + /', '-', $ title);
                 $ title = preg_replace ('| - + |', '-', $ title);
                 $ title = trim ($ title, '-');
                 devolver $ título;
         }
    

    Esta función, así como algunas de las funciones de soporte se pueden encontrar en wp-includes / formatting.php.

    Si está utilizando Rails edge, puede confiar en Inflector.parametrize ; este es el ejemplo de la documentación:

      class Person def to_param "#{id}-#{name.parameterize}" end end @person = Person.find(1) # => # < %= link_to(@person.name, person_path(@person)) %> # => Donald E. Knuth 

    Además, si necesita manejar caracteres más exóticos como acentos (éphémère) en versiones anteriores de Rails, puede usar una mezcla de PermalinkFu y DiacriticsFu :

     DiacriticsFu::escape("éphémère") => "ephemere" DiacriticsFu::escape("räksmörgås") => "raksmorgas" 

    No estoy familiarizado con Ruby on Rails, pero el siguiente es código PHP (no probado). Probablemente pueda traducir esto muy rápidamente a Ruby on Rails si lo encuentra útil.

     $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 

    Espero que esto ayude.

    No sé mucho sobre Ruby o Rails, pero en Perl, esto es lo que haría:

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

    Acabo de hacer una prueba rápida y parece funcionar. Espero que esto sea relativamente fácil de traducir a Ruby.

    Implementación de T-SQL, adaptada de 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 

    Suponiendo que su clase de modelo tiene un atributo de título, simplemente puede anular el método to_param dentro del modelo, como este:

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

    Este episodio de Railscast tiene todos los detalles. También puedes asegurarte de que el título solo contenga caracteres válidos usando esto:

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

    ¿Qué hay de los personajes divertidos? ¿Qué vas a hacer con eso? Umlauts? ¿Puntuación? Estos deben ser considerados. Básicamente, utilizaría un enfoque de lista blanca, a diferencia de los enfoques de lista negra anteriores: describa qué caracteres permitirá, qué caracteres convertirá (¿a qué?) Y luego cambiará el rest por algo significativo (“”) . Dudo que puedas hacer esto en una expresión regular … ¿Por qué no simplemente recorrer los personajes?

    Sé que es una pregunta muy antigua, pero como la mayoría de los navegadores ahora soportan URL unicode , encontré una gran solución en XRegex que convierte todo, excepto las letras (en todos los idiomas, en ‘-‘).

    Eso se puede hacer en varios lenguajes de progtwigción.

    El patrón es \\p{^L}+ y luego solo necesita usarlo para reemplazar todas las letras que no sean ‘-‘.

    Ejemplo de trabajo en node.js con el módulo 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" 

    El código de Brian, en Ruby:

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

    downcase convierte la cadena en minúsculas, strip elimina los espacios en blanco gsub y finales, la primera llamada a gsub gráficamente sustituye espacios por guiones, y la segunda elimina todo lo que no sea una letra o un guión.

    Hay un pequeño complemento de Ruby on Rails llamado PermalinkFu , que hace esto. El método de escape convierte la transformación en una cadena adecuada para una URL . Eche un vistazo al código; ese método es bastante simple.

    Para eliminar caracteres que no sean ASCII , utiliza el iconov lib para traducir a ‘ascii // ignore // translit’ de ‘utf-8’. Los espacios se convierten en guiones, todo se reduce, etc.

    Puedes usar el siguiente método de ayuda. Puede convertir los caracteres 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("--","-"); } 

    Aquí está mi versión (más lenta, pero divertida de escribir) del código de Jeff:

     public static string URLFriendly(string title) { char? prevRead = null, prevWritten = null; var seq = from c in title let norm = RemapInternationalCharToAscii(char.ToLowerInvariant(c).ToString())[0] let keep = char.IsLetterOrDigit(norm) where prevRead.HasValue || keep let replaced = keep ? norm : prevWritten != '-' ? '-' : (char?)null where replaced != null let s = replaced + (prevRead == null ? "" : norm == '#' && "cf".Contains(prevRead.Value) ? "sharp" : norm == '+' ? "plus" : "") let _ = prevRead = norm from written in s let __ = prevWritten = written select written; const int maxlen = 80; return string.Concat(seq.Take(maxlen)).TrimEnd('-'); } public static string RemapInternationalCharToAscii(string text) { var seq = text.Normalize(NormalizationForm.FormD) .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark); return string.Concat(seq).Normalize(NormalizationForm.FormC); } 

    Mi cadena de prueba:

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

    La solución stackoverflow es genial, pero el navegador moderno (excluyendo IE, como siempre) ahora maneja muy bien la encoding utf8:

    enter image description here

    Así que actualicé la solución propuesta:

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

    Código completo en Pastebin

    Editar: Aquí está el código para el método RemapInternationalCharToAscii (que falta en el pastebin).

    Me gustó la forma en que se hace esto sin usar expresiones regulares , así que lo porté a PHP. Acabo de agregar una función llamada is_between para verificar los caracteres:

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

    Ahora, todos los navegadores manejan la encoding muy bien utf8, por lo que puede usar el método WebUtility.UrlEncode , es como HttpUtility.UrlEncode utilizado por @giamin pero funciona fuera de una aplicación web.

    Transmití el código a TypeScript. Se puede adaptar fácilmente a JavaScript.

    Estoy agregando un método .contains al prototipo de String , si está apuntando a los últimos navegadores o ES6 que puede usar. .includes en .includes lugar.

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

    No no no. Todos ustedes están muy equivocados. Excepto por los diacríticos-fu, estás llegando, pero ¿qué pasa con los personajes asiáticos? (Vergüenza con los desarrolladores de Ruby por no considerar a sus hermanos nihonjin ).

    Firefox y Safari muestran caracteres que no son ASCII en la URL y, francamente, se ven geniales. Es agradable admitir enlaces como ‘ http://somewhere.com/news/read/ お ws / / い い い い い い い い い い い

    Así que aquí hay un código PHP que lo hará, pero lo escribí y no lo he probado.

     < ?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; } 

    Ejemplo:

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

    Productos: コ リ and -y- ト ー ス ス -y- ア ー ノ ス ス

    El ‘-y–‘ es porque & se cambia a ‘-y–‘.