使用Regex匹配嵌套模式(使用PHP'递归)

时间:2014-12-19 01:34:53

标签: php regex recursion nested

我目前正在尝试在PHP中编写一个正则表达式,它允许我匹配一个包含自身的特定模式,这些模式是无限制嵌套的。我知道默认的正则表达式无法做到这一点,但PHP的递归模式(http://php.net/manual/de/regexp.reference.recursive.php)应该可以实现。

我有这样的嵌套结构:

<a=5>
    <a=3>
        Foo
        <b>Bar</b>
    </a>
    Baz
</a>

现在我想匹配最外面标签的内容。为了正确匹配第一个开始标记和最后一个结束标记,我需要PHP的递归项(?R)

我尝试了这样的模式:

/<a=5>((?R)|[^<]|<\/?[^a]|<\/?a[a-zA-Z0-9-])*<\/a>/s

这基本上意味着<a=5>,然后是尽可能多的以下内容,然后是</a>

  • 另一个标签(递归地)
  • 任何not-opening-tag字符
  • 任何开始标记,后跟可选的斜杠,后面没有&#34; a&#34;
  • 之前WITH a a,但未完成(后跟至少1个字符)

最后2个案例可能只是一个案例[标签不是名字&#34; a&#34;],但我听说在正则表达式中应该避免这种情况,因为它需要外观并且会有糟糕的表现。

但是,我在RegEx中看到没有错误,但它与给定的字符串不匹配。我想要以下比赛:

    <a=3>
        Foo
        <b>Bar</b>
    </a>
    Baz

以下是使用RegEx的链接:https://www.regex101.com/r/lO1wA6/1

2 个答案:

答案 0 :(得分:2)

你可以使用这个正则表达式来匹配你想要的东西(为方便起见,将正则表达式放在一个字符串文字中):

'~<a=5>(<([a-zA-Z0-9]+)[^>]*>(?1)*</\2>|[^<>]++)*</a>~'

以上是正则表达式的细分:

<a=5>
(
  <([a-zA-Z0-9]+)[^>]*>
  (?1)*
  </\2>
  |
  [^<>]++
)*
</a>

第一部分<([a-zA-Z0-9]+)[^>]*>(?1)*</\2>匹配一对匹配标记及其所有内容。它假定标记的名称由字符[a-zA-Z0-9]组成。在匹配结束标记([a-zA-Z0-9]+)时,会捕获标记的名称</\2>和反向引用。

第二部分[^<>]++匹配标记之外的任何其他内容。请注意,没有处理带引号的字符串,因此根据您的输入,它可能无效。

然后回到递归调用第一个捕获组的例程调用。您会注意到标记可以包含0个或更多其他标记或非标记内容的实例。由于正则表达式的编写方式,此属性也由最外面的<a=5>...</a>对共享。

Demo on regex101

答案 1 :(得分:-1)

试试这个:

<强> PHP

$re = "/(<[^\\/>]+(\\/?)>)*([^<]+)(<\\/\\w+>)*/m";
$str = "<a=5>\n <a=3>\n Foo\n <b/>Bar</b>\n </a>\n Baz\n</a>";

preg_match_all($re, $str, $matches);
var_dump($matches);
// here  

 $matches[1];  //for open tag array
 $matches[2];  //for single tag mark array by ( />)
 $matches[3];  //for inner data array
 $matches[4];  //for close tag array

<强>输出

array (size=5)
  0 => 
    array (size=5)
      0 => string '<a=5>
 ' (length=7)
      1 => string '<a=3>
 Foo
 ' (length=12)
      2 => string '<b/>Bar</b>' (length=11)
      3 => string '
 </a>' (length=6)
      4 => string '
 Baz
</a>' (length=10)
  1 => 
    array (size=5)
      0 => string '<a=5>' (length=5)
      1 => string '<a=3>' (length=5)
      2 => string '<b/>' (length=4)
      3 => string '' (length=0)
      4 => string '' (length=0)
  2 => 
    array (size=5)
      0 => string '' (length=0)
      1 => string '' (length=0)
      2 => string '/' (length=1)
      3 => string '' (length=0)
      4 => string '' (length=0)
  3 => 
    array (size=5)
      0 => string '
 ' (length=2)
      1 => string '
 Foo
 ' (length=7)
      2 => string 'Bar' (length=3)
      3 => string '
 ' (length=2)
      4 => string '
 Baz
' (length=6)
  4 => 
    array (size=5)
      0 => string '' (length=0)
      1 => string '' (length=0)
      2 => string '</b>' (length=4)
      3 => string '</a>' (length=4)
      4 => string '</a>' (length=4)

Live Demo

    $re = "/(<[^\\/>]+\\/?>)*([^<]+)(<\\/\\w+>)*/m";
    $str = "<a=5>fff\n <a=3>\n Foo\n <b/>Bar</b>\n </a>\n Baz\n</a>";

    preg_match_all($re, $str, $matches);
    //var_dump($matches);
    $md="";
    $c=count($matches[1]);
    foreach($matches[1] as $k=>$v){
        if($k!=0){
            $md.=$v.$matches[2][$k].$matches[3][$k];
        }
        else if ($c!=$k+1){
            $md.=$matches[2][$k].$matches[3][$k];
        }
    }   
var_dump($md);

Live

<强>输出

 string 'fff
 <a=3>
 Foo
 <b/>Bar</b>
 </a>
 Baz
</a>' (length=44)