【问题标题】:PHP RegEx for BBCode multi-parameter用于 BBCode 多参数的 PHP RegEx
【发布时间】:2014-05-29 14:45:24
【问题描述】:

此脚本在文本 (online test) 中识别“BBCode”(带有参数和值):

<?php
preg_match_all(
    '#\[(link)(.*?)!?\](.*?)\[\/\\1\]#i', 
    '[link href="http://www.google.com" title="Google" target="_blank"]Google[/link]
     [link href="http://www.facebook.com"]Facebook[/link]
     [link href=\'http://www.twitter.com\' rel="nofollow"]Twitter[/link]', 
    $StrMatches
);

/* $StrMatches[0] = Full tag string
 * $StrMatches[1] = Tag name
 * $StrMatches[2] = tag params string
 * $StrMatches[3] = Tag content
 * */
print_r($StrMatches);


$ParamList = array();

foreach ($StrMatches[2] as $TagParamStr )
{
   preg_match_all('#\s*([^=]+)=[\'|"]([^\'|"]*)[\'|"]#', $TagParamStr, $ParamMatches);
   array_push($ParamList, $ParamMatches);
}

/* $ParamList[0] = Full param string
 * $ParamList[1] = Param name
 * $ParamList[2] = Param value
 * */
print_r($ParamList);

输出:

 Array
(
[0] => Array
    (
        [0] => [link href="http://www.google.com" title="Google" target="_blank"]Google[/link]
        [3] => [link href="http://www.facebook.com"]Facebook[/link]
        [2] => [link href='http://www.twitter.com' rel="nofollow"]Twitter[/link]
    )

[1] => Array
    (
        [0] => link
        [1] => link
        [2] => link
    )

[2] => Array
    (
        [0] =>  href="http://www.google.com" title="Google" target="_blank"
        [1] =>  href="http://www.facebook.com"
        [2] =>  href='http://www.twitter.com' rel="nofollow"
    )

[3] => Array
    (
        [0] => Google
        [1] => Facebook
        [2] => Twitter
    )

) 
Array
(
[0] => Array
    (
        [0] => Array
            (
                [0] =>  href="http://www.google.com"
                [1] =>  title="Google"
                [2] =>  target="_blank"
            )

        [1] => Array
            (
                [0] => href
                [1] => title
                [2] => target
            )

        [2] => Array
            (
                [0] => http://www.google.com
                [1] => Google
                [2] => _blank
            )

    )

[1] => Array
    (
        [0] => Array
            (
                [0] =>  href="http://www.facebook.com"
            )

        [1] => Array
            (
                [0] => href
            )

        [2] => Array
            (
                [0] => http://www.facebook.com
            )

    )

[2] => Array
    (
        [0] => Array
            (
                [0] =>  href='http://www.twitter.com'
                [1] =>  rel="nofollow"
            )

        [1] => Array
            (
                [0] => href
                [1] => rel
            )

        [2] => Array
            (
                [0] => http://www.twitter.com
                [1] => nofollow
            )

    )

)

代码运行良好!但我想用一个正则表达式来优化它。

我怎样才能使它成为一个独特的正则表达式?

对不起我的英语不好:(

【问题讨论】:

    标签: php regex bbcode


    【解决方案1】:

    简答:

    实际上并不像您想象的那样可能,因为正则表达式在很大程度上捕获了一组已定义的组。最理想的方法是用一个匹配捕获param1param2value..但是由于属性的数量发生了变化,这是不可能的。如果我们尝试重复捕获组 1 次以上,它将匹配整个字符串,但仅捕获最后一次出现的 as shown in this quick demo

    但是,您会发现可以将所有这些数据匹配并捕获到一个表达式中。但是,每个链接将被拆分为多个匹配项,每个匹配项都包含一些数据。在我的示例中,我使用捕获组 1 作为属性,捕获组 2 作为属性值,捕获组 3 作为链接值。如果匹配中不存在这些项目,则捕获组将保留为空。


    说明:

    (?# START OF LINK)
    (?:         (?# start non-capture group)
      \[link    (?# match [link literally)
     |          (?# OR)
      (?!^)     (?# assertion to make sure we aren't at the beginning of the string)
      \G        (?# start at the end of last match)
    )           (?# end non-capture group)
    \K          (?# throw everything to the left away)
    
    (?# START OF CAPTURING)
    (?:         (?# start non-capture group)
      \s+       (?# match 1+ whitespace characters)
      ([^=\s]+) (?# capture attribute)
      =         (?# match = literally)
      ["']      (?# match ' or ")
      (.*?)     (?# lazily capture attribute's value)
      ["']      (?# match ' or ")
     |          (?# OR)
      \s*       (?# optionally match whitespace characters)
      \]        (?# match ] literally)
      (.*?)     (?# lazily capture link's value)
      \[/link\] (?# match [/link] literally)
    )           (?# end non-capture group)
    

    Demo

    关键是\G\K。 RegEx 引擎第一次进行匹配时,它从[link 开始,所有匹配的内容都被\K 丢弃。然后我们继续我们的捕获,在那里我们找到并获取一个属性及其值。然后比赛结束。现在它再次返回并找不到[link,因此它使用\G 从最后一个属性重新开始。一切都被\K 再次抛弃。它可能会找到另一个属性,或者它可能会命中交替并将链接的末尾与第三个捕获组匹配。此时正则表达式重新开始时,它会再次找到另一个[link,并从头再来。

    更新:你会在 \G 之前看到 (?!^) 是解决 cmets 问题的方法。 \G 不仅匹配上一次匹配的结尾,还匹配字符串的开头。我们希望在开始匹配内容之前确保我们在链接中([link),所以这意味着我们不希望\G 匹配字符串的开头。这种否定的前瞻将断言这一点。


    PHP:

    $regex = '#(?:\[link|(?!^)\G)\K(?:\s+(\w+)=["\'](.*?)["\']|\s*\](.*?)\[/link\])#si';
    preg_match_all($regex, $html, $matches, PREG_SET_ORDER);
    
    $links = array();
    $reset = true;
    
    foreach($matches as $match) {
        if($reset) {
            $links[] = array(
                'params' => array(),
                'value' => null
            );
    
            $reset = false;
        }
    
        end($links);
        $key = key($links);
    
        if(isset($match[3])) {
            $links[$key]['value'] = $match[3];
            $reset = true;
        } else {
            $links[$key]['params'][$match[1]] = $match[2];
        }
    }
    
    var_dump($links);
    

    输出:

    array(3) {
      [0]=>
      array(2) {
        ["params"]=>
        array(3) {
          ["href"]=>
          string(21) "http://www.google.com"
          ["title"]=>
          string(6) "Google"
          ["target"]=>
          string(6) "_blank"
        }
        ["value"]=>
        string(6) "Google"
      }
      [1]=>
      array(2) {
        ["params"]=>
        array(1) {
          ["href"]=>
          string(23) "http://www.facebook.com"
        }
        ["value"]=>
        string(8) "Facebook"
      }
      [2]=>
      array(2) {
        ["params"]=>
        array(2) {
          ["href"]=>
          string(22) "http://www.twitter.com"
          ["rel"]=>
          string(8) "nofollow"
        }
        ["value"]=>
        string(7) "Twitter"
      }
    }
    

    【讨论】:

    • 看这里3v4l.org/Qou7q 如果主题字符串不以[link.. 开头就不能正常工作。输出:Array ([params] =&gt; Array ( ) [value] =&gt; www.google.com)
    • \[link 更改为.*?\[link,我会在回到我的电脑时更新答案。
    • 我发现另一个问题...如果同一行有两个标签不起作用regex101.com/r/cD4sT1
    • @ar099968 当您尝试在手机上修复问题时会发生这种情况,您会导致更多问题。我将答案更改为使用(?!^),这将解决这些 cmets 中的这两个问题。我还添加了 s 修饰符并对其进行了微调,以便多行链接起作用..我认为这应该涵盖所有内容
    • @sndesign 如果您从正则表达式中删除 \K,则不会从匹配项中删除任何内容。然后,您可以在 foreach 循环中使用 $match[0]reset($match) 引用它,并附加链接每个部分的匹配值。 See the demo!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 2018-06-27
    • 1970-01-01
    相关资源
    最近更新 更多