regex language-agnostic seo friendly-url slug

regex - ¿Cómo genera Stack Overflow sus URLs amigables con SEO?



language-agnostic friendly-url (20)

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

¿Cómo cambias un título para que sea 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 usa en las URLs amigables con SEO en Stack Overflow?

El entorno de desarrollo que estoy usando 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 en la línea.

Estoy usando rutas personalizadas, y principalmente quiero saber cómo modificar la cadena para eliminar todos los caracteres especiales, todo en minúsculas y todo el espacio en blanco se reemplaza.


Ahora todos los navegadores manejan bien la codificación 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.


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


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

  • Los guiones se agregaron de tal manera que se podría agregar uno, y luego se deben 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 de borde. He trabajado alrededor de esto por hibridación de retraso. Si comparas mi código con el de Jeff, la lógica para esto es fácil de seguir.
  • Su enfoque se basa puramente en la búsqueda y omite muchos de los personajes que encontré en los ejemplos al investigar sobre Desbordamiento de pila. Para contrarrestar esto, primero realizo un paso de normalización (la colación AKA mencionada en la pregunta de desbordamiento de Meta Stack, caracteres no US-ASCII eliminados de la URL completa (perfil) ), y luego ignoro cualquier carácter fuera de los rangos aceptables. Esto funciona la mayor parte del tiempo...
  • ... Para cuando no, 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 eliminarlos, 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 la pregunta de desbordamiento de pila ¿ Cómo puedo eliminar los 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)); } /// <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://.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486 /// https://.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; } }

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


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

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

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

Para ver la versión anterior del código que reemplazó (pero es funcionalmente equivalente a, y 5 veces más rápido), vea el historial de revisiones 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 here .


El código de Brian, en Ruby:

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

downcase convierte la cadena en minúsculas, la strip elimina los espacios en blanco gsub y finales, el primer gsub call g subyace en el espacio a los espacios con guiones, y el segundo elimina todo lo que no es una letra o un guión.


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

Para eliminar caracteres que no son ASCII , usa iconv lib para traducir a ''ascii // ignorar // translit'' desde ''utf-8''. Los espacios se convierten en guiones, todo está borrado, etc.


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


La solución es excelente, pero el navegador moderno (excluyendo IE, como siempre) ahora maneja bien la codificación utf8:

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

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


Me gustó la forma en que se hace 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; } }


No estoy familiarizado con Ruby on Rails, pero el siguiente es un 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 me gusta 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 ?"; 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. Esperemos que esto sea relativamente fácil de traducir a Ruby.


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

Firefox y Safari muestran caracteres no ASCII en la URL y, francamente, se ven geniales. Es bueno apoyar enlaces como '' http://somewhere.com/news/read/お前たちはアホじゃないかい ''.

Así que aquí hay algo de 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);

Salidas: コ リ ン -y- ト マ -y- ア ノ ド

El ''-y-'' se debe a que & ''s se cambian a'' -y- ''.


Por si acaso, 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 sofisticados.

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

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


Porté 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, 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; }


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


Querrá configurar una ruta personalizada para apuntar la URL al controlador que la manejará. Ya que está usando Ruby on Rails, aquí hay una introduction al uso de su motor de enrutamiento.

En Ruby, necesitarás una expresión regular como la que ya conoces y esta es la expresión regular que debes usar:

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


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

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

El patrón es //p{^L}+ y luego solo necesitas usarlo para reemplazar todas las letras que no sean de ''-''.

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


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) # => #<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>

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

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


Suponiendo que su clase de modelo tenga un atributo de título, simplemente puede anular el método to_param dentro del modelo, de esta manera:

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 contiene caracteres válidos usando esto:

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


También puede usar esta función de JavaScript para la generación en forma de slug''s (esta se basa en / copiado de 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 }