PHP 正则表达式负后视可变长度替代问题

PHP Regex negative lookbehind variable length alternatives issue

我正在 PHP 中开发一个电报机器人,我必须处理其中只有一些 basic HTML tag are allowed 和所有 <> 和 [=14] 的字符串=] 不属于标签或 HTML 实体的符号必须替换为相应的 HTML 实体(<&lt;>&gt;&&amp;)
示例字符串

<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://www.example.com/" >inline URL</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
yes<b bad<>b> <bad& hi>;<strong >b<a<

我设法使用 Regex 替换了 &<。例如,我在此模式 <(?!(?:(?:\/?)(?:(?:b>)|(?:strong>)|(?:i>)|(?:em>)|(?:code>)|(?:pre>)|(?:a(?:[^>]+?)?>)))) 中使用负前瞻来摆脱 < 符号。

但是我无法构建一个模式来替换不属于任何标签的 > 符号。 PCRE 不支持后视中的不定量词。虽然它允许 lookbehinds 中的备选方案具有不同的长度,但要求每个备选方案具有固定长度。

所以,我尝试使用这种模式(仍然不完整)(?<!(?:(?:<b)|(?:<strong)|(?:<i)|(?:<em)|(?:<code)|(?:<pre>)|(?:<a)))>,其中所有替代项都有固定长度,但它仍然显示 Compilation failed: lookbehind assertion is not fixed length

正确的答案是使用 DOM 解析器。不过,对于一种快速而肮脏(有时更快)的方法,您可以使用 PCRE 实现的 (*SKIP)(*FAIL) 机制:

<[^<>&]+>(*SKIP)(*FAIL)|[<>&]+

参见 a demo on regex101.com


完整的 PHP 演练将是:

<?php
$string = <<<DATA
<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://www.example.com/" >inline URL</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
yes<b bad<>b> <bad& hi>;<strong >b<a<
DATA;

$regex = '~<[^<>&]+>(*SKIP)(*FAIL)|[<>&]+~';
$string = preg_replace_callback($regex,
    function($match) {
        return htmlentities($match[0]);
    },
    $string);

echo $string;
?>

产生:

<b>bold</b>, <strong>bold</strong>
<i>italic</i>, <em>italic</em>
<a href="http://www.example.com/" >inline URL</a>
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width code block</pre>
yes&lt;b bad&lt;&gt;b&gt; &lt;bad&amp; hi&gt;;<strong >b&lt;a&lt;

但是,正如之前在 Whosebug 上多次声明的那样,请考虑改用解析器,毕竟这就是它们的用途。


解析器方式可以是:

$dom = new DOMDocument();
$dom->loadHTML($string, LIBXML_HTML_NOIMPLIED | LIBXML_NOERROR);

echo $dom->saveHTML();

但是,您提供的代码段已损坏,因此正则表达式可能是处理它的唯一方法。

您可以找到合法的特殊符号来转换为这样的实体。

重要的是正确解析标签。
免责声明 - 如果您不按照下面的方式进行操作,则没有理由使用正则表达式,它不会起作用。

在每场比赛中,第 0 组将包含 <、> 或 &
您可以添加更多,请参阅底部的正则表达式

正则表达式
(?:(?><(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\s*(?=>))|(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))>)(*SKIP)(*FAIL)|[<>]|[&](?!(?i:[a-z]+|(?:\#(?:[0-9]+|x[0-9a-f]+)));))

已解释

 (?:
      (?>                           # Atomic group
           <                             # Match tag forms and fail them with skip / fail verbs ( see below )
           (?:
                (?:
                     (?:
                                                        # Invisible content; end tag req'd
                          (                             # (1 start)
                               script
                            |  style
                               #|  head
                            |  object
                            |  embed
                            |  applet
                            |  noframes
                            |  noscript
                            |  noembed 
                          )                             # (1 end)
                          (?:
                               \s+ 
                               (?>
                                    " [\S\s]*? "
                                 |  ' [\S\s]*? '
                                 |  (?:
                                         (?! /> )
                                         [^>] 
                                    )?
                               )+
                          )?
                          \s* >
                     )

                     [\S\s]*? </  \s* 
                     (?= > )
                )

             |  (?: /? [\w:]+ \s* /? )
             |  (?:
                     [\w:]+ 
                     \s+ 
                     (?:
                          " [\S\s]*? " 
                       |  ' [\S\s]*? ' 
                       |  [^>]? 
                     )+
                     \s* /?
                )
             |  \? [\S\s]*? \?
             |  (?:
                     !
                     (?:
                          (?: DOCTYPE [\S\s]*? )
                       |  (?: \[CDATA\[ [\S\s]*? \]\] )
                       |  (?: -- [\S\s]*? -- )
                       |  (?: ATTLIST [\S\s]*? )
                       |  (?: ENTITY [\S\s]*? )
                       |  (?: ELEMENT [\S\s]*? )
                     )
                )
           )
           >
      )                             # End atomic group
      (*SKIP)(*FAIL)

   |                              #or, 
      [<>]                          # Angle brackets

   |                              #or, 
      [&]                           # Ampersand 
      (?!                           # Only if not an entity
           (?i:
                [a-z]+ 
             |  (?:
                     \#
                     (?:
                          [0-9]+ 
                       |  x [0-9a-f]+ 
                     )
                )
           )
           ;     
      )

      # Add more here
 )