【问题标题】:Negative Look-back in preg_replace() with numbered Backreferences, has no effectpreg_replace() 中带有编号的反向引用的负回顾,没有效果
【发布时间】:2014-07-02 05:52:57
【问题描述】:

我有一个正则表达式函数,它将接受我的输入 $text 并返回相同的文本,但任何看起来远程像 Web 地址的内容被格式化为嵌入的 <a href></a>

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

示例输出:
http://www.example.com => &lt;a href="http://www.example.com"&gt;http://www.example.com&lt;/a&gt;
https://www.example.com => &lt;a href="https://www.example.com"&gt;https://www.example.com&lt;/a&gt;
www.example.com => &lt;a href="http://www.example.com"&gt;www.example.com&lt;/a&gt;

现在,我想扩展它,以便当它遇到一个前面有“”的网址时,它会这样做——我试图阻止它来自重新嵌入已经在 href="" 属性中的地址。所以,我尝试添加一个否定的回溯,如下所示:

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

我的理解是负面的回顾不会捕捉到任何东西。不幸的是,添加回溯不仅不会阻止引擎替换以双引号开头的地址上的模式,而且最终还会完全破坏输出。我不知道是不是操作员弄乱了回溯,或者回溯周围的括号实际上是在替换规则中抛弃了我的反向引用。再说一次——我认为他们没有贡献任何捕获的模式。

【问题讨论】:

  • 先发布输入字符串,然后发布您尝试的模式。
  • 因为(http)?(s)?。这两个部分都是可选的,但它们不应该是。
  • 这是我尝试过的两个输入字符串,$text=""www.example.com" 和 $text="www.example.com"。此外,这些部分怎么可能不是可选的? http://www.example.comhttps://www.example.comwww.example.com 都应该被该模式捕获。
  • @user57092 你是说这个regex101.com/r/aW0sT4/1 吗?
  • 哇!不错的正则表达式环境!还有一个更好的解决方案!是的,这就是我想要的行为。将其作为答案,以便我接受。

标签: php regex preg-replace regex-lookarounds


【解决方案1】:

我建议这个不需要任何锚定的正则表达式(因此可以在包含链接和文本的页面上使用):

<a href\s?=\s?"http(s)?://([^"]+)">([^<]+)</a>|(http)?(s)?(://)?((?:[-\w]+\.)+\S+[^,.\s])

用法:

$result = preg_replace('~<a href\s?=\s?"http(s)?://([^"]+)">([^<]+)</a>|(http)?(s)?(://)?((?:[-\w]+\.)+\S+[^,.\s])~', '<a href="http$1$5://$2$7">$3$4$5$6$7</a>', $text);

正则表达式匹配“普通”网址和已经包裹在锚标记之间的网址,但在替换时对它们的处理方式不同。

【讨论】:

    【解决方案2】:

    使用下面的正则表达式在&lt;a&gt;标签内嵌入一个站点地址,不要在已经嵌入的站点地址上进行嵌入。

    正则表达式:

    ^(?!<a href.*$)(http)?(s)?(:\/\/)?(([-\w]+\.)+([^\s]+)+[^,.\s])
    

    换人:

    <a href="http$2://$4">$1$2$3$4</a>
    

    DEMO

    PHP 代码:

    <?php
    $mystring = <<<EOT
    http://www.example.com
    https://www.example.com
    www.example.com
    <a href="http://www.example.com">http://www.example.com</a>
    EOT;
    $pattern = "~^(?!<a href.*$)(http)?(s)?(:\/\/)?(([-\w]+\.)+([^\s]+)+[^,.\s])~m";
    $replacement = '<a href="http$2://$4">$1$2$3$4</a>';
    echo preg_replace($pattern, $replacement, $mystring);
    ?>
    

    输出:

    <a href="http://www.example.com">http://www.example.com</a>
    <a href="https://www.example.com">https://www.example.com</a>
    <a href="http://www.example.com">www.example.com</a>
    <a href="http://www.example.com">http://www.example.com</a>
    

    【讨论】:

    • 您的模式是错误的,并且仅适用于测试示例,因为您使用带有 m 修饰符的行锚^ 的开头。我假设在现实生活中 URL 并不总是在行首。
    • @CasimiretHippolyte 那么如何在没有锚和 $ .op 的情况下编写它并没有提到页面中的 url。
    • 别天真,网址可能在页面中!一种方法是将(&lt;a\s.*?&lt;/a&gt;)|urlpatternpreg_replace_callback 一起使用。定义 group1 时返回它,未定义时返回嵌入“a”标签之间的整个匹配项。
    • 还要注意,在(?!&lt;a href.*$)(http)?(s)?(:\/\/)?(([-\w]... 中,前瞻总是正确的,因为下一个字符不能是尖括号。
    • 卡西米尔的话是真的。我今天学到了很多关于正则表达式的知识:)
    【解决方案3】:

    Avinash Raj 和 Jerry 都提供了与我的问题中所述的参数和要求相匹配的解决方案,并且大大扩展了我对正则表达式的了解。不幸的是,我发现这两种解决方案都开始用iframe src 属性、mailto: 地址(@ 符号之后)等中的句点分隔地址替换字符串。

    在多次尝试让正则表达式补偿所有这些可能性之后,由于 Jerry 解决方案中的第一个替代方案补偿了 &lt;a&gt; 标签内的地址,我得出的结论是,杰克在这里发布了第一个解决方案(以及不再存在)是正确的;我根本无法为我的用户提供协议猜测。所以我改变了要求:协议是必须的,用户必须指定http(s)或ftp(s)。这不是对我的要求,但我想不出更好的解决方案。为此,I devised this (considerably simpler) regex and substitution 规则如下:

    正则表达式:

    ~(?<!["'>])(http|ftp)(s)?://((?:[-\w]+\.)+\S+[^,.\s])~g
    

    换人:

    <a href="$1$2://$3">$3</a>
    

    我将此作为单独的答案发布,因为从技术上讲,它不受 Avinash 和 Jerry 所维护的参数的影响,这些参数构成了我所写的问题。事情的简单事实是,我无法有一个解决方案来处理我无法控制的 iframe 和其他此类元素。但我想不出比要求用户为其链接包含协议更好的方法了。我认为这是最合乎逻辑的妥协,但如果有人有更好的解决方案,我很想听听。

    感谢大家深入了解正则表达式的本质,再次特别感谢 Avinash 向我展示了 regex101。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-03
      • 1970-01-01
      • 1970-01-01
      • 2019-05-25
      • 1970-01-01
      相关资源
      最近更新 更多