关于JavaScript的平原:如何替换URL的联系?

How to replace plain URLs with links?

我使用下面的函数来匹配给定文本中的URL,并将其替换为HTML链接。正则表达式工作得很好,但目前我只替换第一个匹配项。

如何替换所有URL?我想我应该使用exec命令,但我并没有真正想好怎么做。

1
2
3
4
function replaceURLWithHTMLLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i;
    return text.replace(exp,"$1");
}

首先,滚动您自己的regexp来解析URL是一个糟糕的主意。根据RFC,您必须想象这是一个足够常见的问题,有人已经为它编写、调试和测试了一个库。URI是复杂的-请查看node.js中用于URL解析的代码和有关URI方案的wikipedia页面。

在解析URL时,有大量的边缘情况:国际域名、实际(.museum对不存在(.etcTLD)、奇怪的标点符号(包括括号)、URL末尾的标点符号、ipv6主机名等。

我看过很多图书馆,尽管有一些缺点,但还是有一些值得使用的地方:

  • Soapbox的Linkify已经投入了大量的工作,2015年6月的一个主要重构消除了jQuery依赖性。它仍然与IDN有关。
  • 主持人是一个自称更快更瘦的新人。一些IDN问题。
  • autolinker.js列出了非常具体的特性(例如,"将正确处理HTML输入"。实用程序不会更改anchor()标记中的href属性)。当演示可用时,我将对其进行一些测试。

我很快就取消了此任务的库:

  • Django的Urize没有正确处理某些TLD(以下是有效TLD的正式列表)。没有演示。
  • Autolink JS在没有http://的情况下检测不到"www.google.com",因此它不太适合自动链接纯文本中的"临时URL"(没有方案/协议)。
  • 自2009年以来,本·阿尔曼的联系一直没有得到保持。

如果您坚持使用正则表达式,那么最全面的就是来自组件的url regexp,尽管它会通过查看它来错误地检测一些不存在的两个字母tld。


用链接替换URL(回答一般问题)

问题中的正则表达式遗漏了许多边缘情况。在检测URL时,最好使用专门的库来处理国际域名、新的TLD(如.museum、URL内部和末尾的括号和其他标点,以及许多其他边缘情况。有关其他一些问题的解释,请参阅JeffAtwood的博客文章《URL问题》。

The best summary of URL matching libraries is in Dan Dascalescu's Answer +100
(as of Feb 2014)

"使正则表达式替换多个匹配项"(特定问题的答案)

将"g"添加到正则表达式的末尾以启用全局匹配:

1
/ig;

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


我对Travis的代码做了一些小修改(只是为了避免任何不必要的重新声明——但是它非常适合我的需要,非常好的工作!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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, '$1');

    //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$2');

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

    return replacedText;
}


对上面的travis'Linkify()代码进行了一些优化。我还修复了子域类型格式的电子邮件地址不匹配的错误(例如@domain.co.uk)。

另外,我将实现改为原型String类,这样就可以像这样匹配项目:

1
2
3
4
var text = '[email protected]';
text.linkify();

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

不管怎样,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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, '$&')
            .replace(pseudoUrlPattern, '$1$2')
            .replace(emailAddressPattern, '$&');
    };
}


谢谢,这很有帮助。我还想要一些链接类似于URL的东西——作为一个基本要求,它链接类似www.yahoo.com的东西,即使没有http://protocol前缀。所以基本上,如果"www."存在,它将链接它并假定它是http://。我还想把电子邮件变成mailto:links。示例:www.yahoo.com将转换为www.yahoo.com

这是我最后得到的代码(这个页面的代码和我在网上找到的其他东西以及我自己做的其他东西的组合):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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, '$1');

    //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$2');

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

    return replacedText
}

在第二个替换中(^[^/])部分仅替换www.whatever.com,前提是它还没有前缀//-,以避免在第一个替换中链接了URL时出现双重链接。此外,www.whatever.com可能位于字符串的开头,这是regex的第一个"或"条件。

这个插件可以集成为jquery插件,如jesse p上面所示——但是我特别想要一个不在现有dom元素上工作的常规函数,因为我要获取我拥有的文本,然后将其添加到dom中,并且我希望在添加文本之前将其"链接",所以我通过这个函数传递文本。工作很好。


识别URL很难,因为它们经常被标点符号包围,而且用户经常不使用URL的完整形式。有许多javascript函数可以用超链接替换URL,但我在基于python的Web框架django中找不到一个可以和urlize过滤器一样工作的函数。因此,我将django的urlize函数移植到了javascript:

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

一个例子:

1
2
3
urlize('Go to SO (stackoverflow.com) and ask. <grin>',
       {nofollow: true, autoescape: true})
=>"Go to SO (stackoverflow.com) and ask. &lt;grin&gt;"

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


我将roshambo string.linkify()更改为emailAddressPattern以识别a a [email protected]地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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, '$&')
            .replace(pseudoUrlPattern, '$1$2')
            .replace(emailAddressPattern, '$1');
    };
}


最好的脚本:http://benalman.com/projects/javascript-linkify-process-lin/


我在谷歌上搜索到任何更新的东西,然后找到了这个:

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

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

对于普通链接非常有效。


此解决方案与其他许多解决方案一样工作,实际上使用与其中一个相同的regex,但是,除了返回HTML字符串之外,还将返回包含a元素和任何适用文本节点的文档片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 function make_link(string) {
    var words = string.split(' '),
        ret = document.createDocumentFragment();
    for (var i = 0, l = words.length; i < l; i++) {
        if (words[i].match(/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi)) {
            var elm = document.createElement('a');
            elm.href = words[i];
            elm.textContent = words[i];
            if (ret.childNodes.length > 0) {
                ret.lastChild.textContent += ' ';
            }
            ret.appendChild(elm);
        } else {
            if (ret.lastChild && ret.lastChild.nodeType === 3) {
                ret.lastChild.textContent += ' ' + words[i];
            } else {
                ret.appendChild(document.createTextNode(' ' + words[i]));
            }
        }
    }
    return ret;
}

有一些警告,即旧的IE和文本内容支持。

这是一个演示。


如果您需要显示较短的链接(仅限域),但使用相同的长URL,您可以尝试我修改上面发布的Sam Hasler的代码版本。

1
2
3
4
function replaceURLWithHTMLLinks(text) {
    var exp = /(\b(https?|ftp|file):\/\/([-A-Z0-9+&@#%?=~_|!:,.;]*)([-A-Z0-9+&@#%?\/=~_|!:,.;]*)[-A-Z0-9+&@#\/%=~_|])/ig;
    return text.replace(exp,"$3");
}

应该注意有关URI复杂性的警告,但问题的简单答案是:要替换每个匹配项,需要在regex的末尾添加/g标志:/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi


Reg Ex:/(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]*)/ig

1
2
3
4
function UriphiMe(text) {
      var exp = /(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]*)/ig;
      return text.replace(exp,"$1");
}

以下是一些测试字符串:

  • 在www.google.com上找到我
  • 万维网
  • 请登录www.http://www.com查找我。
  • 请访问:http://www.nishantwork.wordpress.com
  • 网址:http://www.nishantwork.wordpress.com
  • 请访问:http://www.nishantwork.wordpress.com
  • https://stackoverflow.com/users/430803/nishant
  • 注:如果您不想通过www作为有效的,只需使用以下注册表项:/(\b((https?|ftp|file):\/\/|(www))[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig


    保持简单!说出你不能拥有的,而不是你能拥有的。)

    如上所述,URL可能非常复杂,特别是在"?"之后。不是所有的都以"www"开头,例如maps.bing.com/something?key=!"£$%^*()&lat=65&lon&lon=20

    所以,与其有一个复杂的regex不能满足所有的边缘情况,而且很难维护,不如用这个简单得多的regex,它在实践中对我很有效。

    比赛

    http(s):// (anything but a space)+

    www. (anything but a space)+

    哪里有"任何东西"是[^'"<>\s]。…基本上是一个贪婪的匹配,继续你会遇到一个空格、引号、尖括号或行尾。

    也:

    记住要检查它是否已经不是URL格式,例如文本包含href="..."src="..."

    添加ref=nofollow(如果适用)

    这个解决方案不如上面提到的库"好",但是简单多了,并且在实践中很好地工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    if html.match( /(href)|(src)/i )) {
        return html; // text already has a hyper link in it
        }

    html = html.replace(
                /\b(https?:\/\/[^\s\(\)\'"\<\>]+)/ig,
               "$1"
                );

    html = html.replace(
                /\s(www\.[^\s\(\)\'"\<\>]+)/ig,
               "$1"
                );

    html = html.replace(
                 /^(www\.[^\s\(\)\'"\<\>]+)/ig,
               "$1"
                );

    return html;


    使用国际域名和星状字符支持进行正确的URL检测并非易事。linkify-it库根据许多条件构建regex,最终大小约为6千字节:)。它比目前公认答案中引用的所有libs更准确。

    请参阅Linkify IT演示,检查所有的边缘案例并测试您的案例。

    如果需要链接HTML源代码,应该首先解析它,并分别迭代每个文本标记。


    我还写了另一个javascript库,它可能对您更有利,因为它对最小可能的误报非常敏感,速度快,大小小。我目前正在积极维护它,所以请在演示页面测试它,看看它将如何为您工作。

    链接:https://github.com/alexcorvi/anchorme.js


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
     * 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, '' + url +"");
        });
      }
      return string.replace("(","<br/>(");
    }

    简单实例


    用HTML链接替换文本中的URL,忽略a href/pre标记中的URL。网址:https://github.com/jimliu/auto-link


    我的解决方案是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    var content ="Visit https://wwww.google.com or watch this video: https://www.youtube.com/watch?v=0T4DQYgsazo and news at http://www.bbc.com";
    content = replaceUrlsWithLinks(content,"http://");
    content = replaceUrlsWithLinks(content,"https://");

    function replaceUrlsWithLinks(content, protocol) {
        var startPos = 0;
        var s = 0;

        while (s < content.length) {
            startPos = content.indexOf(protocol, s);

            if (startPos < 0)
                return content;

            let endPos = content.indexOf("", startPos + 1);

            if (endPos < 0)
                endPos = content.length;

            let url = content.substr(startPos, endPos - startPos);

            if (url.endsWith(".") || url.endsWith("?") || url.endsWith(",")) {
                url = url.substr(0, url.length - 1);
                endPos--;
            }

            if (ROOTNS.utils.stringsHelper.validUrl(url)) {
                let link ="" + url +"";
                content = content.substr(0, startPos) + link + content.substr(endPos);
                s = startPos + link.length;
            } else {
                s = endPos + 1;
            }
        }

        return content;
    }

    function validUrl(url) {
        try {
            new URL(url);
            return true;
        } catch (e) {
            return false;
        }
    }


    尝试以下功能:

    1
    2
    3
    4
    5
    6
    function anchorify(text){
      var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
      var text1=text.replace(exp,"$1");
      var exp2 =/(^|[^\/])(www\.[\S]+(\b|$))/gim;
      return text1.replace(exp2, '$1$2');
    }

    alert(anchorify("Hola amigo! https://www.sharda.ac.in/academics/"));


    上面Travitron答案中的电子邮件检测对我不起作用,因此我将其扩展/替换为以下代码(C代码)。

    1
    2
    3
    4
    5
    // Change e-mail addresses to mailto: links.
    const RegexOptions o = RegexOptions.Multiline | RegexOptions.IgnoreCase;
    const string pat3 = @"([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,6})";
    const string rep3 = @"$1@$2.$3";
    text = Regex.Replace(text, pat3, rep3, o);

    这允许使用"[email protected]"这样的电子邮件地址。


    尝试以下解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function replaceLinkClickableLink(url = '') {
    let pattern = new RegExp('^(https?:\\/\\/)?'+
            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+
            '((\\d{1,3}\\.){3}\\d{1,3}))'+
            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+
            '(\\?[;&a-z\\d%_.~+=-]*)?'+
            '(\\#[-a-z\\d_]*)?$','i');

    let isUrl = pattern.test(url);
    if (isUrl) {
        return `${url}`;
    }
    return url;
    }

    我不得不做相反的事情,把HTML链接变成URL,但是我修改了你的regex,它就像一个魅力,谢谢:)

    1
    2
    3
    var exp = /.*<\/a>/ig;

    source = source.replace(exp,"$1");


    从几个来源输入之后,我现在有了一个很好的解决方案。这与编写自己的替换代码有关。

    回答。

    小提琴。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function replaceURLWithHTMLLinks(text) {
        var re = /(\(.*?)?\b((?:https?|ftp|file):\/\/[-a-z0-9+&@#\/%?=~_()|!:,.;]*[-a-z0-9+&@#\/%=~_()|])/ig;
        return text.replace(re, function(match, lParens, url) {
            var rParens = '';
            lParens = lParens || '';

            // Try to strip the same number of right parens from url
            // as there are left parens.  Here, lParenCounter must be
            // a RegExp object.  You cannot use a literal
            //     while (/\(/g.exec(lParens)) { ... }
            // because an object is needed to store the lastIndex state.
            var lParenCounter = /\(/g;
            while (lParenCounter.exec(lParens)) {
                var m;
                // We want m[1] to be greedy, unless a period precedes the
                // right parenthesis.  These tests cannot be simplified as
                //     /(.*)(\.?\).*)/.exec(url)
                // because if (.*) is greedy then \.? never gets a chance.
                if (m = /(.*)(\.\).*)/.exec(url) ||
                        /(.*)(\).*)/.exec(url)) {
                    url = m[1];
                    rParens = m[2] + rParens;
                }
            }
            return lParens +"" + url +"" + rParens;
        });
    }