【问题标题】:Using regex to parse nested IF statements使用正则表达式解析嵌套的 IF 语句
【发布时间】:2016-04-18 17:29:06
【问题描述】:

我正在以学习和爱好的名义开发自己的模板引擎。我有一个正则表达式,它使用与 TWIG 几乎相同的语法来查找 if 语句。

您可以查看正则表达式 here 以及一些工作示例,然后是我正在尝试制作的一个。

这是正则表达式:

{%\s*if\s+(?<var>(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(?:\.(?:[a-zA-Z0-9_\x7f-\xff]*))*)\s(?:(?<operation>=|!=|<=|<|>=|>)\s(?<var2>(?:(?:(?1)*)(?:\.(?:(?2)*))*)|(?:[0-9]+))\s?)?%}(?<if>(?:(?!{% e(?:lse|nd if) %}).)*)(?:{%\h?else\h?%}(?<else>[\s\S]*?))?{%\h?end if\s?%}

这是它正在处理的数据:

THESE WORK
        {% if thing %}
        stuff
        {% end if %}

        {% if thing %}
        stuff
        {% else %}
        other stuff
        {% end if %}

        {%if thing = thingy %}
        stuff
        {% else %}
        other stuff
        {% end if %}
THIS DOESN'T
        Problem starts here:
        {% if this = that %}
        {% if item.currency = 0 %}
        selected="selected"
        {% else %}
        you
        {% end if %}
        {% end if %}

基本上我希望正则表达式搜索最后一个 {% end if %} 标记,并将其间的所有内容用作我以后可以递归解析的字符串。

另外,作为旁注,将大部分问题信息留在正则表达式测试器的链接中是否合适?还是我也应该在这里(在 SO 上)复制大部分问题?

【问题讨论】:

  • 请在此处发布您的代码,以便为未来的 SO 访问者保留。
  • 相信我,正则表达式只会让你头疼。尝试编写某种解析器。
  • 这看起来像一些Django 模板引擎,对吧?确切的目的是什么?
  • 这是我自己的创作,旨在学习正则表达式和其他一些编程概念。它只是一个简单的模板引擎
  • 在下面发布了一个示例,但这里有一个 regex101 链接可以查看它 -> here

标签: php regex templates


【解决方案1】:

第 1 版

由于您正在试验,经过一番鬼混,为您想出了一个通用的正则表达式。

这可能会增加您当前的知识,并提供一些基础知识。

简介:

在纯正则表达式解决方案中,平衡文本的概念大约是
正则表达式引擎已经消失。它不会填写详细信息。
为此,您必须自己做。

与下降解析器等相比,这是一种较慢的方法。
不同之处在于它不需要放松就知道它在哪里。
因此,这将允许您在遇到错误时继续解析。
从过去的错误中获得更多意义以帮助调试。

在做这种事情时,你应该解析每一个字符。
所以我们解析内容、分隔符开始、核心、结束和错误。

在这种情况下,我们在外部范围留出了 7 个捕获组来浏览信息。

Content - 这是由 if/else/end if 以外的任何内容组成的。

Else - 这是 else 声明

Begin - 这是开始 if

If_Content - 这是if block content

Core - 这是 all between 外部开始和结束。也包含嵌套内容。

End - 这是外部 end if

Error - 这是不平衡错误,它是 ifend if

用法:

在宿主程序中,定义一个名为ParseCore()的函数
这个函数需要传递(或知道)当前的核心字符串。
如果是 c++,它将传递开始和结束字符串迭代器。
无论如何,字符串必须是函数的本地字符串。

在这个函数中,坐在一个while循环中解析字符串。
在每场比赛中,执行 if/else 以查看上面匹配的组。
只能是这些组合

Content

Else

Begin, If_Content, Core, End

Error

只有一组对递归很重要。那是Core 组。
当该组匹配时,您对
ParseCore() 进行递归 函数调用,并将Core 字符串传递给它。

如此重复,直到不再匹配为止。
错误报告、创建结构树和其他任何事情都可以完成
在这个函数中。
您甚至可以在任何时候设置一个全局标志来展开递归调用
并退出。比如说你想STOP出现错误等。

注意:在首次调用 ParseCore() 时,您只需传入整个原始字符串,即可开始解析。

祝你好运!

Expanded

 # (?s)(?:(?<Content>(?&_content))|(?<Else>(?&_else))|(?<Begin>{%\s*if\s+(?<If_Content>(?&_ifbody))\s*%})(?<Core>(?&_core)|)(?<End>{%\s*end\s+if\s*%})|(?<Error>(?&_keyword)))(?(DEFINE)(?<_ifbody>(?>(?!%}).)+)(?<_core>(?>(?<_content>(?>(?!(?&_keyword)).)+)|(?(<_else>)(?!))(?<_else>(?>{%\s*else\s*%}))|(?>{%\s*if\s+(?&_ifbody)\s*%})(?:(?=.)(?&_core)|){%\s*end\s+if\s*%})+)(?<_keyword>(?>{%\s*(?:if\s+(?&_ifbody)|end\s+if|else)\s*%})))

 (?s)                               # Dot-all modifier

 # =====================
 # Outter Scope
 # ---------------

 (?:
      (?<Content>                        # (1), Non-keyword CONTENT
           (?&_content) 
      )
   |                                   # OR,
      # --------------
      (?<Else>                           # (2), ELSE
           (?&_else) 
      )
   |                                   # OR
      # --------------
      (?<Begin>                          # (3), IF
           {% \s* if \s+ 
           (?<If_Content>                     # (4), if content
                (?&_ifbody) 
           )
           \s* %}
      )
      (?<Core>                           # (5), The CORE
           (?&_core) 
        |  
      )
      (?<End>                            # (6)
           {% \s* end \s+ if \s* %}           # END IF
      )
   |                                   # OR
      # --------------
      (?<Error>                          # (7), Unbalanced IF or END IF
           (?&_keyword) 
      )
 )

 # =====================
 #  Subroutines
 # ---------------

 (?(DEFINE)

      # __ If Body ----------------------
      (?<_ifbody>                        # (8)
           (?>
                (?! %} )
                . 
           )+
      )

      # __ Core -------------------------
      (?<_core>                          # (9)
           (?>
                #
                # __ Content ( non-keywords )
                (?<_content>                       # (10)
                     (?>
                          (?! (?&_keyword) )
                          . 
                     )+
                )
             |  
                #
                # __ Else
                # Guard:  Only 1 'else'
                # allowed in this core !!

                (?(<_else>)
                     (?!)
                )
                (?<_else>                          # (11)
                     (?> {% \s* else \s* %} )
                )
             |  
                #
                # IF  (block start)
                (?>
                     {% \s* if \s+ 
                     (?&_ifbody) 
                     \s* %}
                )
                # Recurse core
                (?:
                     (?= . )
                     (?&_core) 
                  |  
                )
                # END IF  (block end)
                {% \s* end \s+ if \s* %}
           )+
      )

      # __ Keyword ----------------------
      (?<_keyword>                       # (12)
           (?>

                {% \s* 
                (?:
                     if \s+ (?&_ifbody) 
                  |  end \s+ if
                  |  else
                )
                \s* %}
           )
      )
 )

示例输入(已删除)
选定的输出(已删除)

伪代码使用示例

bool bStopOnError = false;
regex RxCore(".....");

bool ParseCore( string sCore, int nLevel )
{
    // Locals
    bool bFoundError = false; 
    bool bBeforeElse = true;
    match _matcher;

    while ( search ( core, RxCore, _matcher ) )
    {
      // Content
        if ( _matcher["Content"].matched == true )
          // Print non-keyword content
          print ( _matcher["Content"].str() );

          // OR, Analyze content.
          // If this 'content' has error's and wish to return.
          // if ( bStopOnError )
          //   bFoundError = true;

        else
      // Else 
        if ( _matcher["Else"].matched == true )
        {
            // Check if we are not in a recursion
            if ( nLevel <= 0 )
            {
               // Report error, this 'else' is outside an 'if/end if' block
               // ( note - will only occur when nLevel == 0 )
               print ("\n>> Error, 'else' not in block " + _matcher["Else"].str() + "\n";

               // If this 'else' error will stop the process.
               if ( bStopOnError == true )
                  bFoundError = true;
            }
            else
            {
                // Here, we are inside a core recursion.
                // That means there can only be 1 'else'.
                // Print 'else'.
                print ( _matcher["Else"].str() );

                // Set the state of 'else'. 
                bBeforeElse == false;   
            }
        }

        else
      // Error ( will only occur when nLevel == 0 )
        if ( _matcher["Error"].matched == true )
        {
            // Report error 
            print ("\n>> Error, unbalanced " + _matcher["Error"].str() + "\n";
            // // If this unbalanced 'if/end if' error will stop the process.
            if ( bStopOnError == true )
                bFoundError = true;
        }

        else
      // IF/END IF block
        if ( _matcher["Begin"].matched == true )
        {
            // Analyze 'if content' for error and wish to return.
            string sIfContent = _matcher["If_Content"].str();
            // if ( bStopOnError )
            //   bFoundError = true;
            // else
            // {            
                 // Print 'begin' ( includes 'if content' )
                 print ( _matcher["Begin"].str() );

                 //////////////////////////////
                 // Recurse a new 'core'
                 bool bResult = ParseCore( _matcher["Core"].str(), nLevel+1 );
                 //////////////////////////////

                 // Check recursion result. See if we should unwind.
                 if ( bResult == false && bStopOnError == true )
                     bFoundError = true;
                 else
                     // Print 'end'
                     print ( _matcher["End"].str() );
            // }
        }
        else 
        {
           // Reserved placeholder, won't get here at this time.
        }

      // Error-Return Check
         if ( bFoundError == true && bStopOnError == true )
             return false;
    }

    // Finished this core!! Return true.
    return true;
}

///////////////////////////////
// Main

string strInitial = "...";

bool bResult = ParseCore( strInitial, 0 );
if ( bResult == false )
   print ( "Parse terminated abnormally, check messages!\n" );

【讨论】:

  • 我非常感谢您为此付出的惊人努力和时间。明天我会坐下来,尽可能多地从中学习。再次,非常感谢你!
  • 您提供的正则表达式似乎没有在 else 块上使用。它将它们视为“核心”。你是故意的吗?如果是这样,您是否建议我以另一种方式解析 else 块?
  • @MatthewGoulart - 嗨,马特。是的,我知道else 会回来咬人。这是修复(上面的修订版 1)。 else 现在与 content 分离。在(?&amp;_core) 递归代码中,一个块中只允许有一个else。再多,块(核心)将不匹配。在外部作用域中,else 被剥离为一个单独的实体。核心中只能有 1 个 else,因为这是正则表达式所允许的全部。这是否是错误可以在ParseCore() 函数内部确定。我已经包含了一个详细的使用代码示例。让我知道进展如何..
猜你喜欢
  • 1970-01-01
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多