在一段文本中查找并替换URL,但排除链接标签中的URL。

4

我一直在尝试遍历一个字符串并查找和替换URL链接,这是我迄今为止想出的方法,它似乎在很大程度上运行良好,但是还有一些需要改进的地方。此外,可能不是最佳性能的实现方式。

我在SO上阅读了许多关于此问题的帖子,虽然它对我有很大帮助,但我仍然需要解决其中的一些问题。

我通过两次遍历字符串来实现。第一次我用HTML标记替换bbtags;第二次我遍历字符串并用链接替换文本URL:

$body_str = preg_replace('/\[url=(.+?)\](.+?)\[\/url\]/i', '<a href="\1" rel="nofollow" target="_blank">\2</a>', $body_str);

$body_str = preg_replace_callback(
    '!(?:^|[^"\'])(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?!',
    function ($matches) {
        return strpos(trim($matches[0]), 'thisone.com') == FALSE ?
        '<a href="' . ltrim($matches[0], " \t\n\r\0\x0B.,@?^=%&amp;:/~\+#'") . '" rel="nofollow" target="_blank">' . ltrim($matches[0], "\t\n\r\0\x0B.,@?^=%&amp;:/~\+#'") . '</a>' :
        '<a href="' . ltrim($matches[0], " \t\n\r\0\x0B.,@?^=%&amp;:/~\+#'") . '">' . ltrim($matches[0], "\t\n\r\0\x0B.,@?^=%&amp;:/~\+#'") . '</a>';
    },
    $body_str
);

到目前为止,我发现这个程序有一个问题,就是它倾向于捕捉到“http”之前的字符,例如空格/逗号/冒号等,这会导致链接断裂。因此,我使用preg_replace_callback来解决这个问题,并修剪一些可能破坏链接的不必要字符。
另一个问题是,为了避免匹配已经在A标签中的URL而破坏链接,我目前正在排除以引号或双引号开头的URL,并且我更愿意使用href='|href="进行排除。
非常感谢任何技巧和建议。

请问您能否提供一个样本文本给您的函数,并告诉我们您需要的输出文本是什么? - Gadoma
不要使用正则表达式来解析HTML。请使用适当的HTML解析模块。您无法可靠地使用正则表达式解析HTML,而且在后续过程中会遇到悲伤和挫折。一旦HTML与您的预期不同,您的代码将被破坏。请参阅http://htmlparsing.com/php,了解如何使用已经编写、测试和调试的PHP模块正确解析HTML的示例。 - Andy Lester
1个回答

0

首先,我允许自己对您的代码进行了一些重构,以使其更易于阅读和修改:

function urltrim($str) {
   return ltrim($str, " \t\n\r\0\x0B.,@?^=%&:/~\+#'");
}
function addlink($str,$nofollow=true) {
        return '&lta href="' . urltrim($str) . '"'.($nofollow ? ' rel="nofollow" target="_blank"' : '').'>' . urltrim($str) . '</a>';
}
function checksite($str) {
        return strpos(trim($str), 'thisone.com') == FALSE ?  addlink($str) : addlink($str,false);
}

$body_str = preg_replace('/\[url=(.+?)\](.+?)\[\/url\]/i', '\2', $body_str);

$body_str = preg_replace_callback(
    '!(?:^|[^"\'])(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?!',
       function ($matches) {
        return checksite($matches[0]);
    },
$body_str );

之后,我改变了您处理链接的方式:

  • 我认为URL是一个单词(=在找到空格或\n或\t(=\s)之前的所有字符)
  • 我改变了匹配方法,以匹配字符串前面的href=的存在
    • 如果存在,则不做任何事情,它已经是一个链接
    • 如果没有href=存在,则替换链接
  • 因此,urltrim方法不再有用,因为我不吃掉http之前的第一个字符
  • 当然,我使用urlencode对URL进行编码,避免HTML注入
function urltrim($str) {
    return $str;
}
function addlink($str,$nofollow=true) {
        $url = preg_replace("#(https?)%3A%2F%2F#","$1://",urlencode(urltrim($str)));
        return '<a href="' . $url . '"'.($nofollow ? ' rel="nofollow" target="_blank"' : '').'>' . urltrim($str) . '</a>';
}
function checksite($str) {
        return strpos(trim($str), 'thisone.com') == FALSE ?  addlink($str) : addlink($str,false);
}

$body_str = preg_replace('/\[url=(.+?)\](.+?)\[\/url\]/i', '\2', $body_str);

$body_str = preg_replace_callback(
    '!(|href=)(["\']?)(https?://[^\s]+)!',
    function ($matches) {
        if ($matches[1]) {
            # 如果存在 href=,则不做任何操作,返回原始字符串
            return $matches[0];
        } else {
            # 添加前一个字符(“或')和链接
            return $matches[2].checksite($matches[3]);
        }
    },
    $body_str
);

希望这能对您的项目有所帮助。 如果有帮助,请告诉我们。

再见。


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