如何将纯文本URL替换为链接?

490

我正在使用以下函数来匹配给定文本中的URL并将它们替换为HTML链接。正则表达式非常有效,但目前我只替换了第一个匹配项。

我应该如何替换所有的URL?我猜我应该使用exec命令,但我真的不知道如何做。

function replaceURLWithHTMLLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i;
    return text.replace(exp,"<a href='$1'>$1</a>"); 
}
25个回答

401

首先,自己编写正则表达式来解析URL是一个非常糟糕的想法。你必须想象这是一个足够常见的问题,以至于有人已经为此编写、调试和测试了一个库,根据RFC的规定。 URI是复杂的 - 查看Node.js中URL解析的代码和维基百科关于URI方案的页面。

当涉及解析URL时,存在大量边缘情况:国际域名、实际(.museum)与不存在的(.etc)顶级域名、奇怪的标点符号包括括号、URL末尾的标点符号、IPV6主机名等。

我看了很多 ,尽管存在一些缺点,但有几个值得使用:

我迅速淘汰的图书馆:

如果您坚持使用正则表达式,最全面的是 组件中的URL正则表达式,但它会通过查看它而错误地检测到一些不存在的双字母TLD。

3
遗憾的是,“组件中的URL正则表达式”没有注释,一些解释会很有帮助。 "Autolinker.js" 注释得非常好,并且有测试。链接到Vebjorn Ljosa的回答中的 urlize.js 库看起来也功能齐全,维护得很好,但它没有测试。 - Sam Hasler
1
Regex101.com自动“解释”正则表达式,但祝你好运:) 我还快速发现了一个无效TLD的失败案例(同一链接)。 - Dan Dascalescu
2
有趣的是,没有人提到John Gruber在维护URL正则表达式模式方面的努力。这不是唯一/理想的解决方案,但无论如何都值得研究,如果你正在开发自己的解决方案。只是想把这个作为一个参考添加进来。 - oelna
2
@DanDascalescu 请看一下这个网址 http://markdown-it.github.io/linkify-it/ 。这个库专注于一个任务——在文本中检测链接模式。但我希望它能够做得很好。例如,它具有正确的Unicode支持,包括星际字符。而且它支持国际顶级域名。 - Vitaly
2
对于 Autolinker.js,加1,易于实现,如果您只是寻找快速解决方案,那么这是一个不错的选择。谢谢。 - FlemGrem
显示剩余5条评论

287

用链接替换URL(解决通用问题)

问题中的正则表达式忽略了很多边缘情况。当检测URL时,最好使用专门处理国际域名、新顶级域名(如.museum)、URL内部和结尾处的括号等其他标点符号以及许多其他边缘情况的专业库。请参阅Jeff Atwood的博客文章“URL的问题”,了解其他问题的解释。

有关URL匹配库的最佳概述Dan Dascalescu的答案+100
(截至2014年2月)


“使正则表达式替换多个匹配项”(解决具体问题)

在正则表达式的末尾添加“g”以启用全局匹配:

/ig;

但是,这只解决了正则表达式仅替换第一个匹配项的问题。 不要使用该代码。


178

我对Travis的代码进行了一些小修改(只是为了避免任何不必要的重新声明 - 但它对我的需求很好,所以做得很好!):

function linkify(inputText) {
    var replacedText, replacePattern1, replacePattern2, replacePattern3;

    //URLs starting with http://, https://, or ftp://
    replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

    //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
    replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

    //Change email addresses to mailto:: links.
    replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
    replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return replacedText;
}

1
如何编辑此代码以避免损害嵌入式对象和iframe(YouTube嵌入式对象和iframe)? - Pradyut Bhattacharya
5
这段代码中匹配电子邮件地址的部分存在一个bug。[a-zA-Z]{2,6} 应该被改写成 (?:[a-zA-Z]{2,6})+,以便匹配更复杂的域名,例如 email@example.co.uk - Roshambo
3
没有http://www的URL怎么办?这对那种类型的URL有效吗? - Nathan
1
很棒的代码!有一些小问题。就像Roshambo提到的,它无法处理mailto链接中的.co.uk,而且在www链接(没有http://)之前加上<br />会使它混淆。由于某种原因,它会将br标签插入链接中。我的正则表达式技能不足以修复它,幸运的是第二个问题在我的用例中并不是真正的问题,我也不需要mailto :) - Erik Honn
2
我试图编辑原帖以解决mailto问题,但我必须至少添加6个字符才能进行编辑。但如果你把这行改成:replacePattern3 = /(\w+@[a-zA-Z_]+?(\.[a-zA-Z]{2,6})+)/gim; 那就可以解决mailto问题 :) - yourdeveloperfriend
显示剩余8条评论

78

对Travis上面的Linkify()代码进行了一些优化。我还修复了一个bug,即无法匹配具有子域类型格式的电子邮件地址(例如example@domain.co.uk)。

此外,我更改了实现方式,以原型化String类,这样可以像以下这样匹配项目:

var text = 'address@example.com';
text.linkify();

'http://stackoverflow.com/'.linkify();

无论如何,这是脚本:

if(!String.linkify) {
    String.prototype.linkify = function() {

        // http://, https://, ftp://
        var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;

        // www. sans http:// or https://
        var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;

        // Email addresses
        var emailAddressPattern = /[\w.]+@[a-zA-Z_-]+?(?:\.[a-zA-Z]{2,6})+/gim;

        return this
            .replace(urlPattern, '<a href="$&">$&</a>')
            .replace(pseudoUrlPattern, '$1<a href="http://$2">$2</a>')
            .replace(emailAddressPattern, '<a href="mailto:$&">$&</a>');
    };
}

在我看来,原型函数使事情变得更加清晰 :) - MRVDOG
似乎它不能处理这样的电子邮件地址:info@some-thing.comsome.thing@example.com等。 - Marco Gagliardi
@MarcoGagliardi 很好的发现。已修复。 - Roshambo
1
这对于字符串"git clone https://aaaa@bitbucket.org/ooo/bbb-cc-dd.git"无效。它将字符串分成块,并创建了多个锚点,如下所示:"git clone <a href="https://<a href="mailto:aaaa@bitbucket.org">aaaa@bitbucket.org</a>/ooo/bbb-cc-dd.git">https://<a href="mailto:aaaa@bitbucket.org">aaaa@bitbucket.org</a>/ooo/bbb-cc-dd.git</a>"。 - Jebin
2
它无法处理邮箱用户名中的 + 符号,例如 foo+bar@domain.com。我使用邮箱模式 /[\w.+]+@[a-zA-Z_-]+?(?:\.[a-zA-Z]{2,6})+/gim 进行了修复(请注意第一个括号中的 +),但我不知道是否会影响其他部分。 - Dennis Hackethal
它不能在带有后续<的HTML代码中工作。因此,我添加了:var urlPattern = /\b(?![^<]*>)(?:https?|ftp):\/\/([a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|])/gim;var pseudoUrlPattern = /(^|[^\/])(www\.[a-z0-9-+&@#\/%?=~_|!:,.;]+(\b|$))/gim; - Nixen85

27

感谢,这非常有帮助。我还想要一个可以链接看起来像URL的东西 - 至少能链接像www.yahoo.com这样的东西,即使没有出现http://协议前缀。所以基本上,如果“www。”存在,它将会链接它并假设它是http://。我还想让电子邮件变成mailto:链接。例如:www.yahoo.com将被转换为www.yahoo.com。

下面是我最终使用的代码(结合了此页面和其他我在网上找到的内容,以及我自己做的其他内容):

function Linkify(inputText) {
    //URLs starting with http://, https://, or ftp://
    var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    var replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

    //URLs starting with www. (without // before it, or it'd re-link the ones done above)
    var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    var replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

    //Change email addresses to mailto:: links
    var replacePattern3 = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim;
    var replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return replacedText
}
在第二个替换中, (^|[^/]) 的部分仅在 www.whatever.com 没有前缀 // 的情况下进行替换 -- 如果 URL 已经在第一个替换中链接了,则避免双重链接。此外, www.whatever.com 可能位于字符串开头,这是该正则表达式部分中的第一个“或”条件。
可以像 Jesse P 上面所示一样将其集成为 jQuery 插件 -- 但我特别想要一个常规函数,它不是基于现有的 DOM 元素操作,因为我正在获取文本然后将其添加到 DOM 中,并且我希望在添加之前使文本“链接化”,因此我通过此函数传递文本。效果非常好。

1
第二个模式存在问题,它可以匹配纯粹的“www.domain.com”。当URL中有某种引用时,例如:&location=http%3A%2F%2Fwww.amazon.com%2FNeil-Young%2Fe%2FB000APYJWA%3Fqid%3D1280679945%26sr%3D8-2-ent&tag=tra0c7-20&linkCode=ur2&camp=1789&creative=9325 - 在这种情况下,链接会再次自动链接。一个快速的解决方法是在包含“/”的否定列表后添加字符“f”。因此表达式为:replacePattern2 = /(^|[^/f])(www.[\S]+(\b|$))/gim - Redtopia
上述代码在很多边缘情况下都会失败测试。在检测URL时,最好依赖于专门的库。这就是为什么 - Dan Dascalescu
2
我刚刚在一个字符串上运行了它,其中一些网络链接已经有href链接。在这种情况下,它会失败并搞乱现有的工作链接。 - AdamJones

18

识别URL很棘手,因为它们常常被标点符号包围,而且用户经常不使用URL的完整形式。有许多JavaScript函数可用于将URL替换为超链接,但我无法找到一个像基于Python的Web框架Django中的urlize过滤器一样好用的函数。因此,我将Django的urlize函数移植到JavaScript:

https://github.com/ljosa/urlize.js

一个例子:

urlize('Go to SO (stackoverflow.com) and ask. <grin>', 
       {nofollow: true, autoescape: true})
=> "Go to SO (<a href="http://stackoverflow.com" rel="nofollow">stackoverflow.com</a>) and ask. &lt;grin&gt;"

如果第二个参数为true,则会插入rel="nofollow"。如果第三个参数为true,则会对在HTML中具有特殊含义的字符进行转义。请参见自述文件


还适用于HTML源代码,例如:www.web.com < a href = " https :// github . com " > url < / a > some text - Paulius Zaliaduonis
@Paulius:如果你将选项 django_compatible 设置为 false,它会更好地处理这种情况。 - Vebjorn Ljosa
Django的urlize没有完全支持顶级域名(至少GitHub上的JS端口不支持)。一个能够正确处理顶级域名的库Ben Alman的JavaScript Linkify - Dan Dascalescu
已添加支持检测具有额外顶级域的URL,即使该URL不以“http”或“www”开头。 - Vebjorn Ljosa

15

我在谷歌上搜索了一些最新的东西,然后发现了这个:

$('p').each(function(){
   $(this).html( $(this).html().replace(/((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g, '<a href="$1">$1</a> ') );
});

演示: http://jsfiddle.net/kachibito/hEgvc/1/

对于普通链接非常有效。


这里的“普通链接”是什么?请看您演示的分支:http://jsfiddle.net/hEgvc/27/ 人们会覆盖未覆盖的内容,并以简单的方式完成。根据RFC3986,URI并不容易处理,如果您只想涵盖“普通链接”,我建议至少遵循此正则表达式:^(([^:/?#]+):)?(//([^/?#]))?([^?#])(?([^#]))?(#(.))? - Ivan
2
我指的是任何格式为 http://example.com/folder/folder/folder/https://example.org/blah 等的内容 - 这只是您典型的非疯狂 URL 格式,可以匹配 95-99% 的用例。我将在内部管理区使用它,因此我不需要任何高级功能来捕获边缘情况或哈希链接。 - degenerate
谢谢,你的帮助终于让我得到了我需要的!我只需要稍微修改一下:/(?:^|[^"'>])((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/gi - sammiepls

11

我对Roshambo String.linkify()进行了修改,将emailAddressPattern更改为识别aaa.bbb.@ccc.ddd地址。

if(!String.linkify) {
    String.prototype.linkify = function() {

        // http://, https://, ftp://
        var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;

        // www. sans http:// or https://
        var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;

        // Email addresses *** here I've changed the expression ***
        var emailAddressPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim;

        return this
            .replace(urlPattern, '<a target="_blank" href="$&">$&</a>')
            .replace(pseudoUrlPattern, '$1<a target="_blank" href="http://$2">$2</a>')
            .replace(emailAddressPattern, '<a target="_blank" href="mailto:$1">$1</a>');
    };
}

上面的代码在处理边缘情况时会失败很多测试。在检测URL时,最好依赖于专门的库。这就是为什么 - Dan Dascalescu

9
/**
 * Convert URLs in a string to anchor buttons
 * @param {!string} string
 * @returns {!string}
 */

function URLify(string){
  var urls = string.match(/(((ftp|https?):\/\/)[\-\w@:%_\+.~#?,&\/\/=]+)/g);
  if (urls) {
    urls.forEach(function (url) {
      string = string.replace(url, '<a target="_blank" href="' + url + '">' + url + "</a>");
    });
  }
  return string.replace("(", "<br/>(");
}

简单示例


7

1
很遗憾,自2009年以来作者没有维护它。我正在总结URL解析替代方案 - Dan Dascalescu

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