12

我正在尝试将$_POST['content']所有 url 从 textarea 输入 () 转换为链接。

$content = preg_replace('!(\s|^)((https?://)+[a-z0-9_./?=&-]+)!i', ' <a href="$2" target="_blank">$2</a> ', nl2br($_POST['content'])." ");
$content = preg_replace('!(\s|^)((www\.)+[a-z0-9_./?=&-]+)!i', '<a target="_blank" href="http://$2"  target="_blank">$2</a> ', $content." ");

目标链接格式:www.hello.comhttp(s)://(www).hello.com

但这似乎破坏了任何 iframe、图像或类似内容,

正确的正则表达式将如何忽略 html 标签中的 url?

注意:我知道我需要两个表达式;一个检测没有协议链接(比如www.hello.com,所以我需要添加它),另一个检测带有协议的 url(所以不需要添加)。

4

4 回答 4

18

您的代码在 iframe 等中应该不是什么大问题,因为在那里您"的 URL 前面通常有一个,而不是您的模式需要的空格。

但是,这里有不同的解决方案。<如果您有单个或>在 HTML 注释或类似内容中,它可能无法 100% 工作。但在任何其他情况下,它应该为您提供良好的服务(我不知道这是否对您来说是个问题)。它使用负前瞻来确保>在任何打开之前没有关闭<(因为这意味着您在标签内)。

$content = preg_replace('$(\s|^)(https?://[a-z0-9_./?=&-]+)(?![^<>]*>)$i', ' <a href="$2" target="_blank">$2</a> ', $content." ");
$content = preg_replace('$(\s|^)(www\.[a-z0-9_./?=&-]+)(?![^<>]*>)$i', '<a target="_blank" href="http://$2"  target="_blank">$2</a> ', $content." ");

如果您不熟悉这种技术,这里有一些详细说明。

(?!        # starts the lookahead assertion; now your pattern will only match, if this subpattern does not match
[^<>]      # any character that is neither < nor >; the > is not strictly necessary but might help for optimization
*          # arbitrary many of those characters (but in a row; so not a single < or > in between)
>          # the closing >
)          # ends the lookahead subpattern

请注意,我更改了正则表达式分隔符,因为我现在!在正则表达式中使用。

(\s|^)除非您还需要标签之外的 URL的第一个子模式,您现在也可以删除它(并减少替换中的捕获变量)。

$content = preg_replace('$(https?://[a-z0-9_./?=&-]+)(?![^<>]*>)$i', ' <a href="$1" target="_blank">$1</a> ', $content." ");
$content = preg_replace('$(www\.[a-z0-9_./?=&-]+)(?![^<>]*>)$i', '<a target="_blank" href="http://$1"  target="_blank">$1</a> ', $content." ");

最后......您是否打算不替换最后包含锚点的 URL?例如www.hello.com/index.html#section1?如果您不小心错过了这个,请添加#到您允许的 URL 字符中:

$content = preg_replace('$(https?://[a-z0-9_./?=&#-]+)(?![^<>]*>)$i', ' <a href="$1" target="_blank">$1</a> ', $content." ");
$content = preg_replace('$(www\.[a-z0-9_./?=&#-]+)(?![^<>]*>)$i', '<a target="_blank" href="http://$1"  target="_blank">$1</a> ', $content." ");

编辑:另外,+and%呢?还有一些其他字符可以在不编码的情况下出现在 URL 中。看到这个。 编辑结束

我认为这应该对你有用。但是,如果您可以提供一个显示有效和损坏的 URL 的示例(使用您拥有的代码),我们实际上可以提供经过测试适用于您的所有案例的解决方案。

最后一个想法。正确的解决方案是使用 DOM 解析器。然后你可以简单地将你已经拥有的正则表达式应用于文本节点。但是,您对 HTML 结构的关注非常有限,这使您的问题再次成为常态(只要您在 HTML 注释或页面上的 JavaScript 或 CSS 中没有不匹配的 '<' 或 '>')。如果你确实有这些特殊情况,你真的应该研究一下 DOM 解析器。在这种情况下,这里(到目前为止)提供的解决方案都不是安全的。

于 2012-09-25T20:45:59.607 回答
17
  1. 在我看来,url 是以空格或行尾(垂直空格或所谓的新行)开头https?://和结尾的所有内容。
  2. 由于第一点,图片,链接等不会被替换,因为它们都以“或>开头(除非链接<a href=" http...">以空格开头,但这是无效的html)。
  3. 修饰符/m告诉正则表达式匹配每一行(以便第一点中描述的匹配有效)。
  4. nl2br()替换后应该使用函数(因为链接从行首开始)。
  5. 仅当空间最初存在于 $content 中时才添加前后空间(参见 preg_replace() 函数的第二个参数中的 $1 和 $3)。
  6. 此解决方案支持带有特殊字符的域名,例如www.moški.si

输入:

输入

代码:

<?php

$content =
    preg_replace(
        '~(\s|^)(https?://.+?)(\s|$)~im', 
        '$1<a href="$2" target="_blank">$2</a>$3', 
        $content
    );
$content = 
    preg_replace(
        '~(\s|^)(www\..+?)(\s|$)~im', 
        '$1<a href="http://$2" target="_blank">$2</a>$3', 
        $content
    );
$content = nl2br($content);

输出:

输出

编辑:

https?://不带前缀的链接示例 + 单个preg_replace()调用示例(模式和替换是数组):

$content = 
    preg_replace(
        array(
            '~(\s|^)(www\..+?)(\s|$)~im', 
            '~(\s|^)(https?://)(.+?)(\s|$)~im', 
        ),
        array(
            '$1http://$2$3', 
            '$1<a href="$2$3" target="_blank">$3</a>$4', 
        ),
        $content
    );
$content = nl2br($content);

在此处输入图像描述

于 2012-09-25T22:07:11.497 回答
3

以前已经这样做了数百次。在此页面上,m-buettnerglavić 都可以正常工作,尽管我喜欢 glivic 较短的表达方式。

这是一个很好的 php 资源: http ://code.iamcal.com/php/lib_autolink/

在 Stackoverflow 上重复:

体面的深入文章: -http: //buildinternet.com/2010/05/how-to-automatically-linkify-text-with-php-regular-expressions/

于 2012-10-01T14:45:50.757 回答
3

让我建议一些不那么直截了当的建议:将输入文本拆分为 html 和非 html 部分,然后使用您的正则表达式处理非 html 部分,将文本组合回一个部分。嗯。喜欢:

  <?php
  $chunks = preg_split('/(<.*>)/Ums', $_POST['content'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
  $result = '';
  foreach ($chunks as $chunk) {
    if (substr($chunk,0,1) != '<') {
      /* do your processing on $chunk */
    }
    $result .= $chunk;
  }

一些额外的建议:

  1. 尝试保存源文本并在显示时进行转换。如果将来您发现新的问题/想法,这将允许您改进/修复您的渲染代码。
  2. (https?://)+ 不应该在括号中,你不需要 +,因为它匹配 "https://https://some.com" - 只需输入 https?://[a-z0 -9_./?=&-]+
  3. (www.)+ 也是一样的 :)
于 2012-09-21T22:18:35.703 回答