如何在jQuery中将标题转换为URL slug?

207

我正在使用CodeIgniter开发一个应用程序,我想让表单中的一个字段动态生成URL slug。 我想做的是删除标点符号,将它转换为小写,并用连字符替换空格。例如,Shane's Rib Shack 将变成 shanes-rib-shack。

到目前为止,我已经做到了转换为小写的部分,但替换部分似乎根本没有起作用,而且我不知道如何去掉标点符号:

$("#Restaurant_Name").keyup(function() {
  var Text = $(this).val();
  Text = Text.toLowerCase();
  Text = Text.replace('/\s/g','-');
  $("#Restaurant_Slug").val(Text);  
});

2
不是jQuery,而是看看库“speakingurl”或“node-slug”。 - Kevin Wheeler
1
...或者 slugify - x-yuri
26个回答

489

我不知道“slug”这个术语来自何处,但我们可以这样说:

function convertToSlug(Text) {
  return Text.toLowerCase()
             .replace(/ /g, '-')
             .replace(/[^\w-]+/g, '');
}

第一个replace方法将空格更改为连字符,第二个replace方法将除字母数字、下划线或连字符以外的任何内容删除。

如果您不想把“像 - 这样”的东西变成“like---this”,那么可以使用以下代码:

function convertToSlug(Text) {
  return Text.toLowerCase()
             .replace(/[^\w ]+/g, '')
             .replace(/ +/g, '-');
}

第一次替换将删除连字符(但不删除空格),第二次替换将连续的空格压缩成一个连字符。

因此,“like - this”变为“like-this”。


1
不要忘记添加“/”,如果需要多个目录分隔。 - Val
10
“slug”一词源自WordPress。 - Brynner Ferreira
35
дёәйҒҝе…ҚеҮәзҺ°иҝһз»ӯеӨҡдёӘиҝһеӯ—з¬ҰпјҢжҲ‘дҪҝз”ЁдәҶtext.toLowerCase().replace(/ /g,'-').replace(/[-]+/g, '-').replace(/[^\w-]+/g,'');пјҢиҖҢдёҚжҳҜйҖүйЎ№2гҖӮйҖүйЎ№2дјҡжҠҠ"th--is"еҸҳжҲҗ"this"гҖӮ - Ryan Allen
我该如何在URL中允许点号? - Liron Harel
你应该使用$.trim函数来避免空格。$.trim($(this).val()).toLowerCase().replace(/ /g,'_').replace(/[^\w-]+/g,'') - Ankit Vishwakarma
显示剩余3条评论

134
var slug = function(str) {
  str = str.replace(/^\s+|\s+$/g, ''); // trim
  str = str.toLowerCase();

  // remove accents, swap ñ for n, etc
  var from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;";
  var to   = "aaaaaeeeeeiiiiooooouuuunc------";
  for (var i = 0, l = from.length; i < l; i++) {
    str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
  }

  str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
           .replace(/\s+/g, '-') // collapse whitespace and replace by -
           .replace(/-+/g, '-'); // collapse dashes

  return str;
};

尝试一下

slug($('#field').val())

原作者: http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/


编辑: 包含更多语言特殊字符的扩展内容:

var from = "ÁÄÂÀÃÅČÇĆĎÉĚËÈÊẼĔȆĞÍÌÎÏİŇÑÓÖÒÔÕØŘŔŠŞŤÚŮÜÙÛÝŸŽáäâàãåčçćďéěëèêẽĕȇğíìîïıňñóöòôõøðřŕšşťúůüùûýÿžþÞĐđßÆa·/_,:;";
var to   = "AAAAAACCCDEEEEEEEEGIIIIINNOOOOOORRSSTUUUUUYYZaaaaaacccdeeeeeeeegiiiiinnooooooorrsstuuuuuyyzbBDdBAa------";

7
但不正确。在德语文本中,ü 应该替换为 ue 等等。 - feklee
6
“不正确”这个词在德语(和可能一些其他语言)中是正确的,但在其他语言中却没有问题。对于一个英文网站,我希望“Márföldi”(匈牙利姓氏)被转换成“marfoldi”,而不是像德国人那样写成“marfoeldi”。 - michalstanko
1
为确保跨平台兼容性,您可能需要将 from="\u00E3\u00E0\u00E1\u00E4\u00E2\u1EBD\u00E8\u00E9\u00EB\u00EA\u00EC\u00ED\u00EF\u00EE\u00F5\u00F2\u00F3\u00F6\u00F4\u00F9\u00FA\u00FC\u00FB\u00F1\u00E7\u00B7/_,:;" 进行替换。 - Mike Godin
2
不错的解决方案!但是缺少了斯堪的纳维亚半岛上的“å”。 - Fredric
1
你可以将 İĞŞığş 添加到 from 变量中,并将它们转换为 IGSigs 以支持土耳其语字符。 - CemilF
1
嗨@Taranttini,如果您使用str.toLocaleLowerCase(),您可以删除替换中的所有大写字母 :) - Andreas

27
首先,正则表达式不应该有引号,所以'/\s/g'应该改为/\s/g 为了将所有非字母数字字符替换为破折号,可以使用以下代码(使用您的示例代码):
$("#Restaurant_Name").keyup(function() {
  var Text = $(this).val();
  Text = Text.toLowerCase();
  Text = Text.replace(/[^a-zA-Z0-9]+/g,'-');
  $("#Restaurant_Slug").val(Text);        
});

那应该就可以解决问题了...


警告,此功能不支持à、è、ì、ò、ù。 - Channel

18

希望这能挽救某人的一天...

/* Encode string to slug */
function convertToSlug( str ) {
    
  //replace all special characters | symbols with a space
  str = str.replace(/[`~!@#$%^&*()_\-+=\[\]{};:'"\\|\/,.<>?\s]/g, ' ')
           .toLowerCase();
    
  // trim spaces at start and end of string
  str = str.replace(/^\s+|\s+$/gm,'');
    
  // replace space with dash/hyphen
  str = str.replace(/\s+/g, '-');   
  document.getElementById("slug-text").innerHTML = str;
  //return str;
}
<input 
  type="text" 
  onload="convertToSlug(this.value)" 
  onkeyup="convertToSlug(this.value)"
  value="Try it Yourself" 
/>
<p id="slug-text"></p>


这也适用于非英语字符。 - kiranvj

11

结合此处的各种答案并使用normalize提供良好的覆盖范围。保持操作顺序以逐步清理URL。

function clean_url(s) {
    return s.toString().normalize('NFD').replace(/[\u0300-\u036f]/g, "") //remove diacritics
            .toLowerCase()
            .replace(/\s+/g, '-') //spaces to dashes
            .replace(/&/g, '-and-') //ampersand to and
            .replace(/[^\w\-]+/g, '') //remove non-words
            .replace(/\-\-+/g, '-') //collapse multiple dashes
            .replace(/^-+/, '') //trim starting dash
            .replace(/-+$/, ''); //trim ending dash
}

normlize('NFD') 将带重音符号的字符拆分为基本字母和附加符号(即重音部分)。replace(/[\u0300-\u036f]/g, "") 去除所有附加符号,只留下基本字母。其余内容均在内联注释中解释。


1
谢谢。这很简单,并且在我的测试用例中表现良好,甚至包括越南字符。const testCases = [ { input: 'is it a good slug', expect: 'is-it-a-good-slug' }, { input: '-----is-----it-----a-----good-----slug-----', expect: 'is-it-a-good-slug' }, { input: 'CÔNG cha như núi Thái Sơn', expect: 'cong-cha-nhu-nui-thai-son' }, { input: '-Haha--Nhất-Nguyễn', expect: 'haha-nhat-nguyen' } ] - Phat Tran
如果有人在输入字符时立即触发此操作,请省略最后一行,即用于 onblur 的替换结束破折号的代码 .replace(/-+$/, '');。这样做将允许用户完全输入它们的slug,否则它将始终在下一个字母之前删除“-”。 - alkadelik
另外,".replace(/[^\w-]+/g, ' ') //remove non-words" 应该改为 ".replace(/[^\w-]+/g, ' ')",而 .replace(/--+/g, '-') //collapse multiple dashes 应该改为 ".replace(/--+/g, '-')"。 - alkadelik
如果你将normalize('NFD')改为normalize('NFC')(或者只使用normalize(),因为"NFC是默认值),你就不用再运行一次replace()来移除分解的变音符号。 - cababunga
仅使用normalize('NFC')是不够的!!!即使空格也会保留。您说的“建议”是什么意思 @cababunga - Eduard Void
1
@EduardVoid,这个评论是关于去除变音符号的。当然,调用normalize()并不是整个解决方案。 - cababunga

11
我找到了一个完整而好的英语解决方案。
function slugify(string) {
  return string
    .toString()
    .trim()
    .toLowerCase()
    .replace(/\s+/g, "-")
    .replace(/[^\w\-]+/g, "")
    .replace(/\-\-+/g, "-")
    .replace(/^-+/, "")
    .replace(/-+$/, "");
}

它的一些使用示例:

slugify(12345);
// "12345"

slugify("  string with leading   and   trailing whitespace    ");
// "string-with-leading-and-trailing-whitespace"

slugify("mIxEd CaSe TiTlE");
// "mixed-case-title"

slugify("string with - existing hyphens -- ");
// "string-with-existing-hyphens"

slugify("string with Special™ characters");
// "string-with-special-characters"

感谢安德鲁·斯图尔特


8
你所需要的只是一个加号 :)
$("#Restaurant_Name").keyup(function() {
  var Text = $(this).val();
  Text = Text.toLowerCase();
  var regExp = /\s+/g;
  Text = Text.replace(regExp,'-');
  $("#Restaurant_Slug").val(Text);        
});

8

看一下这个用于清洗URL的slug函数,由Sean Murphy在https://gist.github.com/sgmurphy/3095196开发。

/**
 * Create a web friendly URL slug from a string.
 *
 * Requires XRegExp (http://xregexp.com) with unicode add-ons for UTF-8 support.
 *
 * Although supported, transliteration is discouraged because
 *     1) most web browsers support UTF-8 characters in URLs
 *     2) transliteration causes a loss of information
 *
 * @author Sean Murphy <sean@iamseanmurphy.com>
 * @copyright Copyright 2012 Sean Murphy. All rights reserved.
 * @license http://creativecommons.org/publicdomain/zero/1.0/
 *
 * @param string s
 * @param object opt
 * @return string
 */
function url_slug(s, opt) {
    s = String(s);
    opt = Object(opt);

    var defaults = {
        'delimiter': '-',
        'limit': undefined,
        'lowercase': true,
        'replacements': {},
        'transliterate': (typeof(XRegExp) === 'undefined') ? true : false
    };

    // Merge options
    for (var k in defaults) {
        if (!opt.hasOwnProperty(k)) {
            opt[k] = defaults[k];
        }
    }

    var char_map = {
        // Latin
        'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': 'C', 
        'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I', 
        'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 
        'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 
        'ß': 'ss', 
        'à': 'a', 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 
        'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 
        'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 
        'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 
        'ÿ': 'y',

        // Latin symbols
        '©': '(c)',

        // Greek
        'Α': 'A', 'Β': 'B', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'H', 'Θ': '8',
        'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N', 'Ξ': '3', 'Ο': 'O', 'Π': 'P',
        'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y', 'Φ': 'F', 'Χ': 'X', 'Ψ': 'PS', 'Ω': 'W',
        'Ά': 'A', 'Έ': 'E', 'Ί': 'I', 'Ό': 'O', 'Ύ': 'Y', 'Ή': 'H', 'Ώ': 'W', 'Ϊ': 'I',
        'Ϋ': 'Y',
        'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h', 'θ': '8',
        'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': '3', 'ο': 'o', 'π': 'p',
        'ρ': 'r', 'σ': 's', 'τ': 't', 'υ': 'y', 'φ': 'f', 'χ': 'x', 'ψ': 'ps', 'ω': 'w',
        'ά': 'a', 'έ': 'e', 'ί': 'i', 'ό': 'o', 'ύ': 'y', 'ή': 'h', 'ώ': 'w', 'ς': 's',
        'ϊ': 'i', 'ΰ': 'y', 'ϋ': 'y', 'ΐ': 'i',

        // Turkish
        'Ş': 'S', 'İ': 'I', 'Ç': 'C', 'Ü': 'U', 'Ö': 'O', 'Ğ': 'G',
        'ş': 's', 'ı': 'i', 'ç': 'c', 'ü': 'u', 'ö': 'o', 'ğ': 'g', 

        // Russian
        'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo', 'Ж': 'Zh',
        'З': 'Z', 'И': 'I', 'Й': 'J', 'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', 'О': 'O',
        'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U', 'Ф': 'F', 'Х': 'H', 'Ц': 'C',
        'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sh', 'Ъ': '', 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu',
        'Я': 'Ya',
        'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 'ж': 'zh',
        'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o',
        'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c',
        'ч': 'ch', 'ш': 'sh', 'щ': 'sh', 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu',
        'я': 'ya',

        // Ukrainian
        'Є': 'Ye', 'І': 'I', 'Ї': 'Yi', 'Ґ': 'G',
        'є': 'ye', 'і': 'i', 'ї': 'yi', 'ґ': 'g',

        // Czech
        'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R', 'Š': 'S', 'Ť': 'T', 'Ů': 'U', 
        'Ž': 'Z', 
        'č': 'c', 'ď': 'd', 'ě': 'e', 'ň': 'n', 'ř': 'r', 'š': 's', 'ť': 't', 'ů': 'u',
        'ž': 'z', 

        // Polish
        'Ą': 'A', 'Ć': 'C', 'Ę': 'e', 'Ł': 'L', 'Ń': 'N', 'Ó': 'o', 'Ś': 'S', 'Ź': 'Z', 
        'Ż': 'Z', 
        'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's', 'ź': 'z',
        'ż': 'z',

        // Latvian
        'Ā': 'A', 'Č': 'C', 'Ē': 'E', 'Ģ': 'G', 'Ī': 'i', 'Ķ': 'k', 'Ļ': 'L', 'Ņ': 'N', 
        'Š': 'S', 'Ū': 'u', 'Ž': 'Z', 
        'ā': 'a', 'č': 'c', 'ē': 'e', 'ģ': 'g', 'ī': 'i', 'ķ': 'k', 'ļ': 'l', 'ņ': 'n',
        'š': 's', 'ū': 'u', 'ž': 'z'
    };

    // Make custom replacements
    for (var k in opt.replacements) {
        s = s.replace(RegExp(k, 'g'), opt.replacements[k]);
    }

    // Transliterate characters to ASCII
    if (opt.transliterate) {
        for (var k in char_map) {
            s = s.replace(RegExp(k, 'g'), char_map[k]);
        }
    }

    // Replace non-alphanumeric characters with our delimiter
    var alnum = (typeof(XRegExp) === 'undefined') ? RegExp('[^a-z0-9]+', 'ig') : XRegExp('[^\\p{L}\\p{N}]+', 'ig');
    s = s.replace(alnum, opt.delimiter);

    // Remove duplicate delimiters
    s = s.replace(RegExp('[' + opt.delimiter + ']{2,}', 'g'), opt.delimiter);

    // Truncate slug to max. characters
    s = s.substring(0, opt.limit);

    // Remove delimiter from ends
    s = s.replace(RegExp('(^' + opt.delimiter + '|' + opt.delimiter + '$)', 'g'), '');

    return opt.lowercase ? s.toLowerCase() : s;
}

1
在评论中有人说:“由于char_map对象中存在重复项,因此在IE11-浏览器中使用use strict将无法正常工作。” - BBaysinger

6
function slugify(text){
  return text.toString().toLowerCase()
    .replace(/\s+/g, '-')           // Replace spaces with -
    .replace(/[^\u0100-\uFFFF\w\-]/g,'-') // Remove all non-word chars ( fix for UTF-8 chars )
    .replace(/\-\-+/g, '-')         // Replace multiple - with single -
    .replace(/^-+/, '')             // Trim - from start of text
    .replace(/-+$/, '');            // Trim - from end of text
}

*基于https://gist.github.com/mathewbyrne/1280286

现在,您可以转换此字符串:

Barack_Obama       Барак_Обама ~!@#$%^&*()+/-+?><:";'{}[]\|`

into:

barack_obama-барак_обама

应用于你的代码:

$("#Restaurant_Name").keyup(function(){
    var Text = $(this).val();
    Text = slugify(Text);
    $("#Restaurant_Slug").val(Text);
});

不确定为什么这个答案没有被选为正确答案。许多答案不考虑从slug中删除#或? - 这样会损坏URL。即使是最常用的React库也没有实现此功能。这个答案非常简单,却是通用的。 - Vladimir Marton

6

注意:如果你不关心反对已接受答案的论点,只是想找到答案,请跳过下一节,在最后会找到我的建议答案

已接受的答案有几个问题(我认为):

1)第一个函数代码片段:

不考虑多个连续空格

输入:is it a good slug

收到的结果:---is---it---a---good---slug---

预期结果:is-it-a-good-slug

不考虑多个连续破折号

输入:-----is-----it-----a-----good-----slug-----

收到的结果:-----is-----it-----a-----good-----slug-----

预期结果:is-it-a-good-slug

请注意,此实现不处理外部破折号(或空格),无论它们是多个连续的还是单个字符(就我所理解的 slug 及其用法而言)都是无效的

2)第二个函数代码片段:

它通过将多个连续空格转换为单个-来处理,但这还不够,因为外部(在字符串的开头和结尾处)空格也是同样处理的,因此is it a good slug将返回-is-it-a-good-slug-

它还完全从输入中删除破折号,将类似于--is--it--a--good--slug--'的东西转换为isitagoodslug,由 @ryan-allen 在评论中提供的代码片段解决了这个问题,但外部破折号问题仍未解决

现在我知道没有 slugs 的标准定义,而已接受的答案可能会完成用户发布问题时要求的工作,但这是关于 JS 中 slugs 的最流行的 SO 问题,因此必须指出这些问题,而且(关于 完成所需的工作!)想象一下键入这个 URL 的可怕情况(www.blog.com/posts/-----how-----to-----slugify-----a-----string-----),甚至只是重定向到它而不是像(www.blog.com/posts/how-to-slugify-a-string)这样的东西,我知道这是一个极端情况,但嘿,这就是测试的目的。

在我看来,一个更好的解决方案如下:

const slugify = str =>
  str
  .trim()                      // remove whitespaces at the start and end of string
  .toLowerCase()              
  .replace(/^-+/g, "")         // remove one or more dash at the start of the string
  .replace(/[^\w-]+/g, "-")    // convert any on-alphanumeric character to a dash
  .replace(/-+/g, "-")         // convert consecutive dashes to singuar one
  .replace(/-+$/g, "");        // remove one or more dash at the end of the string

现在可能有一位能够将其转换为一行表达式的正则表达式高手,我并不是正则表达式专家,也不是说这是最好、最紧凑或性能最佳的解决方案,但希望它能完成任务。


这个程序有一个缺陷,即将非字母数字字符转换为破折号可能会在行首恢复一个破折号。但请不要让它成为一行代码。这个很容易理解! - Timo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接