如何使用正则表达式捕获嵌套的{%if ...%} {%endif%}语句

时间:2015-02-08 08:30:59

标签: php regex preg-replace-callback

这就是我现在所拥有的:

/{% if(.+?) %}(.*?){% endif %}/gusi

它可以捕获多个if语句等等。

IMG:http://image.xesau.eu/2015-02-07_23-22-11.png

但是当我做嵌套的那些时,if if in if,它会在{%endif%}的第一次出现时停止

IMG:http://image.xesau.eu/2015-02-08_09-29-43.png

有没有办法像{%if ...%}语句那样捕获尽可能多的{%endif%}语句,如果有,怎么办?

2 个答案:

答案 0 :(得分:5)

不要使用regexen,请使用现有的Twig解析器。这是我编写的一个提取器示例,它解析自定义标记并提取它们:https://github.com/deceze/Twig-extensions/tree/master/lib/Twig/Extensions/Extension/Gettext

词法分析器的工作是将Twig源代码转换为对象;如果你需要进入这个过程,你可以扩展它:

class My_Twig_Lexer extends Twig_Lexer {

    ...

    /**
     * Overrides lexComment by saving comment tokens into $this->commentTokens
     * instead of just ignoring them.
     */
    protected function lexComment() {
        if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
            throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename);
        }
        $value = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
        $token = new Twig_Extensions_Extension_Gettext_Token(Twig_Extensions_Extension_Gettext_Token::COMMENT, $value, $this->lineno);
        $this->commentTokens[] = $token;
        $this->moveCursor($value . $match[0][0]);
    }

    ...

}

Twig通常会丢弃Twig注释节点,这个词法分析器会保存它们。

但是,您主要关注的是使用解析器:

$twig   = new Twig_Environment(new Twig_Loader_String);
$lexer  = new My_Twig_Lexer($twig);
$parser = new Twig_Parser($twig);

$source = file_get_contents($file);
$tokens = $lexer->tokenize($source);
$node   = $parser->parse($tokens);
processNode($node);

$node这里是节点树的根节点,它以面向对象的方式表示T​​wig源,所有节点都已正确解析。您只需要处理这棵树而不必担心用于生成它的确切语法:

 processNode(Twig_NodeInterface $node) {
      switch (true) {
          case $node instanceof Twig_Node_Expression_Function :
              processFunctionNode($node);
              break;
          case $node instanceof Twig_Node_Expression_Filter :
              processFilterNode($node);
              break;
      }

      foreach ($node as $child) {
          if ($child instanceof Twig_NodeInterface) {
              processNode($child);
          }
      }
 }

只需遍历它,直到找到您正在寻找的节点类型并获取其信息。玩一下吧。这个示例代码可能有点过时,也可能没有过时,你必须深入研究Twig解析器源代码才能理解它。

答案 1 :(得分:3)

将您的模式更改为recursive pattern

几乎是微不足道的
{% if(.+?) %}((?>(?R)|.)*?){% endif %}

工作示例:https://regex101.com/r/gX8rM0/1

然而, 这是一个坏主意:模式缺少很多情况,这些都是解析器中的错误。只是几个常见的例子:

  • 评论

    {% if aaa %}
    123
    <!-- {% endif %} -->
    {% endif %}
    
  • 字符串文字

    {% if aaa %}a = "{% endif %}"{% endif %}
    
    {% if $x == "{% %}" %}...{% endif %}
    
  • 转义字符(您确实需要转义字符,对吧?):

    <p>To start a condition, use <code>\{% if aaa %}</code></p>
    
  • 输入无效
    如果解析器在无效输入上能够相对较好地工作,并且指向错误的正确位置,那将是很好的。