【问题标题】:RegEx: nested tags正则表达式:嵌套标签
【发布时间】:2014-11-21 15:37:42
【问题描述】:

我使用正则表达式,但我不能这样做。我已经创建了自己的引擎,例如 DLE。 我有 [a]、[/a]、[b]、[/b] 等标签。我使用像

这样的正则表达式
'\\[a\\](.*?)\\[/a\\]'si 

或喜欢

'\\[a\\](.*?)(\\[/a\\])+'si

它不能按我想要的方式工作。 我需要接收:

from '[a]delete[/a]' : ''

from '[a][b]delete[/b][/a]' : '',

from '[a][a]delete[/a][/a]' : '', with '\\[a\\](.*?)\\[/a\\]'si it returns '[/a]'

from '[b][a]delete[/a][b]' : '[b][/b]'

from '[b][a]delete[/a][b] [a]delete[/a]' : '[b][/b]'

from '[a]
          delete
          [a]
              [b]delete[/b]
          [/a]
          delete
      [/a]
      [b]
          [a]delete[/a]
          nodelete
      [/b]'
      :
      '[b]
          nodelete
      [/b]'

帮我创建正确的正则表达式!

【问题讨论】:

  • 该语法似乎接近 HTML。请注意HTML can't be parsed with regex。也许这也适用于你的语法。
  • 您的意见是什么?还是您帖子中的信息是您的输入?如果是这样,您的预期输出是什么?正如@Oriol 指出的那样,您无法使用 RegEx 解析 HTML(和类似结构),您可能需要一个可能使用 RegEx 的递归函数
  • 我可以为我的任务编写自己的函数。但我想知道:我可以在这个任务中使用正则表达式吗?
  • 我的帖子模板是:"'input' : 'output'"
  • 输出似乎是有语法错误的标签。为什么“nodelete”是最后一个的输出?

标签: javascript php regex preg-replace nested-loops


【解决方案1】:

PHP方式

您可以使用 php 一次性完成。但是要处理嵌套标签,就需要用到递归特性,所以不能用Javascript做同样的事情:

$text = preg_replace('~\s*\[a](?:[^[]+|\[(?!/?a])|(?R))*+\[/a]\s*~', '', $text);

online demo

模式详情

~                  # pattern delimiter
\s*                # only here to remove leading whitespaces
\[a]
(?:                # non-capturing group: describes the allowed 
                   # content between tags:
    [^[]+          #    - all that is not a [
  |                #  OR
    \[ (?!/?a])    #    - a [ that is not the begining of an opening
                   #       or closing "a" tag
  |                #  OR
    (?R)           #    - recurse to the whole pattern
)*+                # repeat the group zero or more times (possessive quantifier)
\[/a]
\s*                # to remove trailing spaces
~

Javascript方式

由于 ECMAScript 正则表达式引擎无法使用递归功能,解决此问题的一种方法是使用针对最里面的“a”标签的多次替换。为了完成这个任务,你可以使用这个禁止嵌套“a”标签的模式(注意这个模式和之前的非常相似,(?=(subpattern*))\1 的语法只模拟一个所有格量词)

text = text.replace(/\s*\[a\](?=((?:[^\[]+|\[(?!\/?a\]))*))\1\[\/a\]\s*/g, '');

您需要应用此替换,直到没有更多要替换的标签。您可以使用闭包作为增加计数器的替换来检测替换的数量,然后将所有内容放入do...while 循环中。示例:

var counter;    
do {
    counter = 0;
    text = text.replace(/\s*\[a\](?=((?:[^\[]+|\[(?!\/?a\]))*))\1\[\/a\]\s*/g, function (m) {counter++; return '';});
} while (counter>0)

【讨论】: