如何将锚点标签添加到从文本输入获取的URL中

11

我想能够获取用户在评论字段中输入的文本,并检查是否存在URL类型的表达式,如果存在,则在显示评论时添加锚标签(到URL)。

我在服务器端使用PHP,客户端使用JavaScript(带有jQuery),所以我应该等到显示评论前才检查URL吗?还是在插入数据库之前就添加锚标签?

所以

<textarea id="comment">check out blahblah.com or www.thisthing.co.uk or http://checkthis.us/</textarea>  

变成

<div id="commentDisplay">check out <a href="blahblah.com">blahblah.com</a> or <a href="www.thisthing.co.uk">www.thisthing.co.uk</a> or <a href="http://checkthis.us/">http://checkthis.us/</a></div>

3
我理解你的意图,但是你的示例在语法上是无效的,我只能提醒你:你必须使用协议(http://)来指定外部URL,否则它们将变成相对链接并指向你自己的域名!因此,请使用 http://blahblah.com 等形式。 - BalusC
1
如果在将评论插入数据库之前进行这种操作,如果有人想编辑自己的帖子,就会出现问题:其中会有一些HTML代码。因此,要么在显示时进行操作,要么在数据库中存储两个版本的评论(一个“干净”的版本和一个“转换/丰富”版本)。 - Pascal MARTIN
@BalusC 你说得对,我本来想在显示的时候更改它,但我太爱复制粘贴了,忘记了。 - Douglas
8个回答

22

首先,提一个要求。在将数据写入数据库之前,请不要这样做。相反,在向最终用户显示数据之前,请执行此操作。这将减少所有混淆,并为您未来提供更多的灵活性。

以下是一个在网上找到的示例

$text = preg_replace('@(https?://([-\w\.]+)+(:\d+)?(/([-\w/_\.]*(\?\S+)?)?)?)@', '<a href="$1">$1</a>', $text);

这里有一个更加详细的来自 daringfireball.net:

/**
 * Replace links in text with html links
 *
 * @param  string $text
 * @return string
 */
function auto_link_text($text)
{
   $pattern  = '#\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))#';
   $callback = create_function('$matches', '
       $url       = array_shift($matches);
       $url_parts = parse_url($url);

       $text = parse_url($url, PHP_URL_HOST) . parse_url($url, PHP_URL_PATH);
       $text = preg_replace("/^www./", "", $text);

       $last = -(strlen(strrchr($text, "/"))) + 1;
       if ($last < 0) {
           $text = substr($text, 0, $last) . "&hellip;";
       }

       return sprintf(\'<a rel="nowfollow" href="%s">%s</a>\', $url, $text);
   ');

   return preg_replace_callback($pattern, $callback, $text);
}

这个应该可以工作,但我担心可能会有延迟(因为它将一次显示很多评论)。 - Douglas
试一试吧。我怀疑你不会遇到任何明显的延迟。 - Sampson
那真的很棒(第二个函数) - Alex Coplan
第一个函数无法处理URL中的破折号,例如此页面的URL:https://dev59.com/questions/vnI-5IYBdhLWcg3wO1rl。 - Pat Zabawa
2
@JonathanSampson 当我使用第二个函数时,只输入域名而不带“http://”时,它会将当前域名附加到开头。例如:如果我输入www.google.com,则显示为www.mydomain.com/www.google.com。 - user1846348

13

我改进了Jonathan Sampson的正则表达式选项,使其更灵活地识别域名(不需要http(s)来确定)。

function hyperlinksAnchored($text) {
    return preg_replace('@(http)?(s)?(://)?(([-\w]+\.)+([^\s]+)+[^,.\s])@', '<a href="http$2://$4">$1$2$3$4</a>', $text);
}

适用于这些URL(并成功地省略尾随的句点或逗号):

http://www.google.com/
https://www.google.com/.
www.google.com
www.google.com.
www.google.com/test
google.com
google.com,
google.com/test
123.com/test
www.123.com.au
ex-ample.com
http://ex-ample.com
http://ex-ample.com/test-url_chars.php?param1=val1.
http://ex-ample.com/test-url_chars?param1=value1&param2=val+with%20spaces

希望能对某些人有所帮助。


1
我一直在寻找一个适用于你的示例中展示的所有不同情况的答案。感谢您抽出时间与社区分享这个!做得好! - zeckdude
1
@user1846348 是正确的。它也不适用于像 httpfun.com 这样的域名。 - Dex
这应该被调整,使得 (http)?(s)?(://)? 变成 (https?://)? -- 我相信这将解决之前提到的问题。(仍需要更新 $1、$2 等。) - Nathan J.B.
1
这也可以当前捕获数字,例如399.99。 - Nathan J.B.

3

以下是我用来格式化文本中所有链接的代码,包括带和不带协议的电子邮件、URL。

public function formatLinksInText($text)
{
    //Catch all links with protocol      
    $reg = '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/\S*)?/'; 
    $formatText = preg_replace($reg, '<a href="$0" style="font-weight: normal;" target="_blank" title="$0">$0</a>', $text);

    //Catch all links without protocol
    $reg2 = '/(?<=\s|\A)([0-9a-zA-Z\-\.]+\.[a-zA-Z0-9\/]{2,})(?=\s|$|\,|\.)/';
    $formatText = preg_replace($reg2, '<a href="//$0" style="font-weight: normal;" target="_blank" title="$0">$0</a>', $formatText);

    //Catch all emails
    $emailRegex = '/(\S+\@\S+\.\S+)\b/';
    $formatText = preg_replace($emailRegex, '<a href="mailto:$1" style="font-weight: normal;" target="_blank" title="$1">$1</a>', $formatText);
    $formatText = nl2br($formatText);
    return $formatText;
}

2

优化Markd的答案,避免小数、百分数、数字日期(10.3.2001)、省略号和IP地址上的链接:

    function addLinks($text) {
    return preg_replace('@(http)?(s)?(://)?(([a-zA-Z])([-\w]+\.)+([^\s\.]+[^\s]*)+[^,.\s])@', '<a target="ref" href="http$2://$4">$1$2$3$4</a>', $text);
}

适用于:

http://www.google.com/
https://www.google.com/.
www.google.com
www.google.com.
www.google.com/test
google.com
google.com,
google.com/test
www.123.com.au
ex-ample.com
http://ex-ample.com
http://ex-ample.com/test-url_chars.php?param1=val1.
http://ex-ample.com/test-url_chars?param1=value1&param2=val+with%20spaces

不适用于:

123.com/test (无'www'的数字域名)
保持舆论压力......平均水平(省略号)
从3.79万上升3.8%至3.94万(百分比和小数)
Andrew Brooke编辑-07.08.2013 19:57 (dd.mm.yyyy日期)
10.1.1.1 (IP地址)


1

个人而言,我会在显示之前使用JS标记它,这似乎比自己编辑用户的评论更专业和可持续。


1

我宁愿在服务器端完成这个操作。Javascript 有一个“延迟”;它只在整个 HTML DOM 树被加载并在 Web 浏览器中显示时运行。因此,在 URL 被识别和解析之前可能需要一段时间(虽然很短)。客户端可能会看到链接在他仍然面对内容的时候立即被替换。这可能会导致客户端产生“wtf?”的体验。这现在太容易与广告/垃圾邮件/间谍软件相关联了。您应该尽可能避免这种情况。不要使用 JS 在 onload 时更改内容,而是仅在用户控制的事件(onclick、onchange、onfocus 等)期间执行。使用服务器端语言在保存或显示之前更改内容。

因此,只需寻找一个 PHP 脚本,它可以解析文本(或使用正则表达式)以基于纯文本中的 URL 构建完整的链接。您可以在这里找到很多。祝好运。


我同意关于"WTF语句"和滞后的说法,尽管我可能需要调整数据库中保存评论的列以容纳更多字符,以考虑由PHP添加的字符。 - Douglas

0

对已接受答案进行了一些小更新,也适用于没有协议的链接(没有http(s)://的链接)- 之前它们被链接但作为相对链接,这是无法工作的。

我还添加了一些注释作为文档。

/**
 * Replace links in text with html links
 *
 * @param  string $text Text to add links to
 * @return string Text with links added
 */
function auto_link_text( $text )
{
    $pattern = "#\b((?:https?:(?:/{1,3}|[a-z0-9%])|[a-z0-9.\-]+[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)/)(?:[^\s()<>{}\[\]]+|\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\))+(?:\([^\s()]*?\([^\s()]+\)[^\s()]*?\)|\([^\s]+?\)|[^\s`!()\[\]{};:'.,<>?«»“”‘’])|(?:(?<!@)[a-z0-9]+(?:[.\-][a-z0-9]+)*[.](?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|Ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\b/?(?!@)))#";
    return preg_replace_callback( $pattern, function( $matches ) {
        $url = array_shift( $matches );

        // force http if no protocol included
        if ( !startsWith( $url, 'http' ) ) {
            $url = 'http://' . $url;
        }

        // make link text from url - removing protocol
        $text = parse_url( $url, PHP_URL_HOST ) . parse_url( $url, PHP_URL_PATH );
        
        // remove the www from the link text
        $text = preg_replace( "/^www./", "", $text );

        // remove any long trailing path from url
        $last = -( strlen( strrchr( $text, "/" ) ) ) + 1;
        if ( $last < 0 ) {
            $text = substr( $text, 0, $last ) . "&hellip;";
        }

        // update 
        return sprintf(
            '<a rel="nowfollow" target="_blank" href="%s">%s</a>', 
            $url, 
            $text
        );
    }, $text );
}

/**
 * Check strings for starting match
 *
 * @param  string $string String to check.
 * @param  string $startString Startin string to match.
 * @return boolean Wether string begins with startString. 
 */
function startsWith( $string, $startString ) 
{ 
    $len = strlen($startString); 
    return (substr($string, 0, $len) === $startString); 
}

0

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