代码之家  ›  专栏  ›  技术社区  ›  Junior

如何用正则表达式表示可选组?

  •  0
  • Junior  · 技术社区  · 5 年前

    我正试图使用C#来使用regex解析文本。

    我有以下文字 例1

    Fn.If(first condition) 
       When the first condition is valid! This is a required section
    Fn.ElseIf(some second condition)
       When the second condition is valid! This is an optional section
    Fn.ElseIf(third second condition)
       When the third condition is valid! This is an optional section
    Fn.Else
        Catch all! This is an optional section
    Fn.End
    

    我希望能够将每个部分提取为3组,这样最终的结果如下

    • (1A组):Fn.If
    • (1B组):第一个条件
    • (1C组):当第一个条件有效时!这是必修课
    • (2A组):Fn.ElseIf
    • (2B组):第二个条件
    • (组2C):当第二个条件有效时!这是一个可选的部分
    • (3A组):Fn.ElseIf
    • (3B组):第三个条件
    • (3C组):当第三个条件有效时!这是一个可选的部分
    • (4A组):Fn.Else
    • (第4B组):全力以赴!这是一个可选的部分
    • (C组):Fn.结束

    正如您可以从评论中看到的,第1组(A/B/C)必须与最后一组一起存在,以使模式有效。然而,两者之间的所有组都是可选的,这意味着它们可能存在或可能不存在。

    除了上面的文本示例之外,模式还应该能够解析以下文本 例2

    Fn.If(first condition) 
       When first condition is valid! This is a required section
    Fn.EndIf
    

    或文本 例3

    Fn.If(first condition) 
       When first condition is valid! This is a required section
    Fn.Else
        Catch all! This is an optional section
    Fn.EndIf
    

    我能做到

    1. (Fn\.If\s*)\((.+?)\)([\s\S]+)(Fn\.EndIf) 使用文本示例2
    2. (Fn\.ElseIf\s*)\((.+?)\)([\s\S]+) 将返回 Fn.ElseIf(...)....
    3. (Fn\.Else)([\s\S]+) 将捕获 Fn.Else.....

    然而,我正在努力把所有3个模式放在一起,同时认为第2行可以有零个或多个组,然后是第3行的一个或没有。

    我试过下面的方法,但没用。为了便于阅读,我在每组后面加了一行,只是为了提问。

    (Fn\.If\s*)\((.+?)\)([\s\S]+)
    ((Fn\.ElseIf\s*)\((.+?)\)([\s\S]+))*
    ((Fn\.Else)([\s\S]+))?
    (Fn\.EndIf)
    
    1 回复  |  直到 5 年前
        1
  •  0
  •   Dai    5 年前

    我觉得使用一个单一的正则表达式会使事情变得太复杂-所以这里有一个基于有限状态机的方法,仍然使用正则表达式来捕获每一行。

    void Main()
    {
        const String input = 
    @"Fn.If(first condition)
       When the first condition is valid! This is a required section
    Fn.ElseIf(some second condition)
       When the second condition is valid! This is an optional section
    Fn.ElseIf(third second condition)
       When the third condition is valid! This is an optional section
    Fn.Else
        Catch all! This is an optional section
    Fn.End  
        ";
    
        Regex rIf     = new Regex( @"^Fn\.If\((.+)\)\s*$" );
        Regex rElseIf = new Regex( @"^Fn\.ElseIf\((.+)\)\s*$" );
        Regex rElse   = new Regex( @"^Fn\.Else\s*$" );
        Regex rEnd    = new Regex( @"^Fn\.End\s*$" );
    
        String[] lines = input.Split(new String[] { "\r\n" }, StringSplitOptions.None );
    
        List<Statement> statements = new List<Statement>();
    
        String type = null;
        String condition = null;
        StringBuilder sb = new StringBuilder();
    
        State state = State.Outside;
        foreach( String line in lines )
        {
            switch( state )
            {
            case State.Outside:
    
                Match mIf = rIf.Match( line );
                if( mIf.Success )
                {
                    type = "Fn.If";
                    condition = mIf.Groups[1].Value;
    
                    state = State.InIf;
                }
    
                break;
            case State.InIf:
            case State.InElseIf:
    
                Match mElseIf = rElseIf.Match( line );
                if( mElseIf.Success )
                {
                    statements.Add( new Statement( type, condition, sb.ToString() ) );
                    sb.Length = 0;
    
                    state = State.InElseIf;
                    type = "Fn.ElseIf";
                    condition = mElseIf.Groups[1].Value;
                }
                else
                {
                    Match mElse = rElse.Match( line );
                    if( mElse.Success )
                    {
                        statements.Add(new Statement(type, condition, sb.ToString()));
                        sb.Length = 0;
    
                        state = State.InElse;
                        type = "Fn.Else";
                        condition = null;
                    }
                    else
                    {
                        sb.Append( line );
                    }
                }
    
                break;
    
            case State.InElse:
    
                Match mEnd = rEnd.Match(line);
                if (mEnd.Success)
                {
                    statements.Add(new Statement(type, condition, sb.ToString()));
                    sb.Length = 0;
    
                    state = State.Outside;
                    type = null;
                    condition = null;
                }
                else
                {
                    sb.Append( line );
                }
    
                break;
            }
        }
    
        statements.Dump();
    }
    
    class Statement
    {
        public Statement( String type, String condition, String contents )
        {
            this.Type = type;
            this.Condition = condition;
            this.Contents = contents;
        }
    
        public String Type { get; }
        public String Condition { get; }
        public String Contents { get; }
    }
    
    // Define other methods and classes here
    enum State
    {
        Outside,
        InIf,
        InElseIf,
        InElse
    }
    

    在Linqpad中运行将提供以下输出:

    enter image description here

        2
  •  0
  •   Jacek Rojek    5 年前

    一个正则表达式就行了

    这是正则表达式的python版本,但它应该可以翻译为C#

    关键是对所有匹配项使用相同的捕获组

    (Fn\.[A-Za-z]+[^\(\n]*)((\((.+?)\)(?<=\)))?([\s\S]*?)(?=Fn\.))?

    用所有3个例子测试

    在线预览: https://regex101.com/r/VqNlMm/1