C# 当分隔符可以位于标记中时,使用正则表达式标记

C# 当分隔符可以位于标记中时,使用正则表达式标记,c#,regex,C#,Regex,我正在解析C#中的一些输入,并且我正在使用正则表达式处理 免责声明:我不是正则表达式专家,但我学到了更多 我有一个如下所示的输入字符串: ObjectType[property1=value1,property2=value2,property3=AnotherObjectType[property4=some value4]] (一个人为的值,但重要的是这些值可以嵌套) 我正在执行以下操作来标记字符串: Regex Tokenizer = new Regex(@"([=\[\]])|(,\s)

我正在解析C#中的一些输入,并且我正在使用正则表达式处理

免责声明:我不是正则表达式专家,但我学到了更多

我有一个如下所示的输入字符串:

ObjectType[property1=value1,property2=value2,property3=AnotherObjectType[property4=some value4]]

(一个人为的值,但重要的是这些值可以嵌套)

我正在执行以下操作来标记字符串:

Regex Tokenizer = new Regex(@"([=\[\]])|(,\s)");
string[] tokens = Tokenizer.Split(s);
这让我有98%的机会。这将在已知分隔符上拆分字符串,逗号后跟空格

上述示例中的令牌为:

ObjectType
[
property1
=
value1
,   
property2
=
value2
,
property3
=
AnotherObjectType
[
property4
=
some value4
]
]
但我有两个问题:

1) 属性值可以包含逗号。这是一个有效的输入:

ObjectType [property1=This is a valid value, and should be combined,, property2=value2, property3=AnotherObjectType [property4=value4]]
我希望property1=之后的令牌为:

This is a valid value, and should be combined,
我想保留令牌中的空白。目前,当找到逗号时,它将被拆分

2) 拆分时,逗号标记包含空格。如果可能的话,我想把它处理掉,但这是一个不那么重要的优先事项

我尝试了各种各样的选择,它们都让我部分达到了目的。最接近我的是:

    Regex Tokenizer = new Regex(@"([=\[\]])|(,\s)|([\w]*\s*(?=[=\[\]]))|(.[^=]*(?=,\s))");
为了匹配分隔符,逗号后跟空白,单词字符后跟文字前的空白,文本后跟逗号和空白(不包括=号)

当我得到匹配项而不是调用split时,我得到以下结果:

ObjectType
[
property1
=
value1
,   
property2
=
value2
,
property3
=
AnotherObjectType
[
property4
=
value4
]
]
请注意property4中缺少的信息。更复杂的输入有时会在标记中包含右括号,如:value4] 我不知道为什么会这样。关于如何改进这一点有什么想法吗

谢谢,
Phil

您可以使用两个正则表达式和一个递归函数来执行此操作,其中一个警告是:必须转义特殊字符。据我所见,
“=”
“[”
“]”
具有特殊意义,因此如果希望这些字符作为属性值的一部分出现,必须在它们之前插入一个
“\”
。请注意,逗号不被视为“特殊”。
“property=“
字符串”前面的逗号将被忽略,但除此之外,它们不会以特殊方式处理(事实上,在属性之间是可选的)

输入
ObjectType
[
属性1=值1,值\=值2
property2=value2\[property2\=这不是对象\],property3=
另一个ObjectType[property4=some
值4]]
正则表达式 用于发现“复杂”类型的正则表达式(以类型名称开头,后跟方括号)。正则表达式包括一个平衡方括号的机制,以确保每个开放式括号与封闭式括号配对(以便比赛不会结束得太早或太迟):

^\s*(?\w+)\s*[(?([^\[\].\\\\[\\\].\\\[\\\].[?)(?)(?)*(?(深度)(?!))\]\s*$
用于在复杂类型中发现属性的正则表达式。请注意,这还包括平衡方括号,以确保子复杂类型的属性不会被父类意外使用

(?\w+)\s*=\s*(?([^\[\].\\\\[\\\].[^\\\\\\].[^\\\\\\].(?)(?)*((?(深度)(?!))(?=$)(?)?
代码


如果你无法逃脱分隔符,那么我怀疑即使是人类也无法解析这样的字符串。例如,人类如何可靠地知道属性3的值是应该被视为文本字符串还是复杂类型?

这是用词法分析器和解析器工具最容易回答的问题。许多人认为,对于这些“简单”类型来说,它们太复杂了用例,尽管我总是发现它们更清晰、更容易推理。你不会陷入愚蠢的if逻辑

对于C#,似乎是一些好的。参见

在你的例子中,你有一个语法,这就是你如何根据上下文定义不同标记之间的交互。而且,你有在你的语言和工具链中实现这个语法的细节。语法相对容易定义,你已经非正式地这样做了。细节是棘手的部分。不是吗如果您有一个框架,可以读取一些定义的方式写出语法位,然后生成代码来实际执行它,那该多好啊

简而言之,这就是这些工具的工作原理。这些文档非常简短,所以请通读所有文档,花点时间提前阅读将非常有帮助

本质上,您将声明一个扫描器和解析器。扫描器接收一个文本流/文件,并将其与各种正则表达式进行比较,直到找到匹配项。该匹配项作为标记传递给解析器。然后,下一个标记被匹配并传递,一轮又一轮,直到文本流清空

每个匹配的令牌都可以附加任意C#代码,解析器中的每个规则也是如此


我通常不使用C#,但我已经编写了很多词法分析器和语法分析器。不同语言的原理都是一样的。这是解决问题的最佳方法,在你的职业生涯中会一次又一次地帮助你。

不要觉得你需要用一个正则表达式来解决这个问题;使用多个正则表达式来解决这个问题是非常好的如果使问题更容易解决,请解决此问题。除非性能成为问题,但我希望先更正它。属性值是否可以包含“=”、“[”或“]'没有嵌套对象的字符?关于换行符呢?基本上,你确定你能可靠地解析这种格式吗?从技术上讲,属性值可以包含所有的分隔符,尽管这不太可能。@Patrick-这是一个愚蠢的问题,但是对于多个正则表达式,该方法是什么?你会吗你想试试正则表达式计算器类吗?
private static Regex ComplexTypeRegex = new Regex( @"^\s*(?<TypeName>\w+)\s*\[(?<Properties>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*(?(Depth)(?!)))\]\s*$" );
private static Regex PropertyRegex = new Regex( @"(?<PropertyName>\w+)\s*=\s*(?<PropertyValue>([^\[\]]|\\\[|\\\]|(?<!\\)\[(?<Depth>)|(?<!\\)\](?<-Depth>))*?(?(Depth)(?!))(?=$|(?<!\\)\]|,?\s*\w+\s*=))" );

private static string Input = 
    @"ObjectType" + "\n" +
    @"[" + "\n" +
    @"    property1=value1,val\=value2   " + "\n" +
    @"    property2=value2 \[property2\=this is not an object\], property3=" + "\n" +
    @"        AnotherObjectType [property4=some " + "\n" + 
    @"value4]]";

static void Main( string[] args )
{
    Console.Write( Process( 0, Input ) );
    Console.WriteLine( "\n\nPress any key..." );
    Console.ReadKey( true );
}

private static string Process( int level, string input )
{
    var l_complexMatch = ComplexTypeRegex.Match( input );

    var l_indent = string.Join( "", Enumerable.Range( 0, level * 3 ).Select( i => " " ).ToArray() );

    var l_output = new StringBuilder();

    l_output.AppendLine( l_indent + l_complexMatch.Groups["TypeName"].Value );

    foreach ( var l_match in PropertyRegex.Matches( l_complexMatch.Groups["Properties"].Value ).Cast<Match>() )
    {
        l_output.Append( l_indent + "@" + l_match.Groups["PropertyName"].Value + " = " );

        var l_value = l_match.Groups["PropertyValue"].Value;

        if ( Regex.IsMatch( l_value, @"(?<!\\)\[" ) )
        {
            l_output.AppendLine();
            l_output.Append( Process( level + 1, l_value ) );
        }
        else
        {
            l_output.AppendLine( "\"" + l_value + "\"" );
        }

    }

    return l_output.ToString();
}