当分隔符可以在标记中时,使用RegEx进行标记化

本文关键字:使用 RegEx 分隔符 | 更新日期: 2023-09-27 17:50:32

我正在用c#解析一些输入,我在RegEx处理方面遇到了瓶颈。

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

我有一个像这样的输入字符串:

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中缺少的信息。更复杂的输入有时会在令牌中包含右括号,如:我不知道为什么会这样。有什么改进的办法吗?

谢谢,菲尔。

当分隔符可以在标记中时,使用RegEx进行标记化

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

对于c#, GPLEX和GPPG似乎是一些不错的。请参阅此处了解您可能想要使用它们的原因。

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

这就是这些工具的工作原理。文档很短,所以通读所有的文档,提前花点时间会有很大的帮助。

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

每个匹配的标记都可以附加任意c#代码,解析器中的每个规则也是如此。

我通常不使用c#,但我写了不少词法分析器和解析器。不同语言的原则是一样的。这是你的问题的最佳解决方案,并且将在你的职业生涯中一次又一次地帮助你。

您可以使用两个正则表达式和一个递归函数来实现这一点,但需要注意的是:特殊字符必须转义。从我所看到的,"=", "[""]"具有特殊含义,所以如果您希望它们作为属性值的一部分出现,则必须在这些字符之前插入"'"。注意,逗号不被认为是"特殊的"。"property="字符串前的逗号将被忽略,但不会以任何特殊方式处理它们(实际上,在属性之间是可选的)。

输入
ObjectType
[
    property1=value1,val'=value2   
    property2=value2 '[property2'=this is not an object'], property3=
        AnotherObjectType [property4=some 
value4]]
正则表达式

用于发现"复杂"类型的正则表达式(以类型名称开头,后跟方括号)。regex包含一种平衡方括号的机制,以确保每个左括号都与右括号配对(以便匹配不会太早或太晚结束):

^'s*(?<TypeName>'w+)'s*'[(?<Properties>([^'[']]|'''[|''']|(?<!'')'[(?<Depth>)|(?<!'')'](?<-Depth>))*(?(Depth)(?!)))']'s*$

用于在复杂类型中发现属性的正则表达式。注意,这还包括平衡的方括号,以确保子复杂类型的属性不会被父类型意外地使用。

(?<PropertyName>'w+)'s*='s*(?<PropertyValue>([^'[']]|'''[|''']|(?<!'')'[(?<Depth>)|(?<!'')'](?<-Depth>))*?(?(Depth)(?!))(?=$|(?<!'')']|,?'s*'w+'s*=))

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();
}

输出
ObjectType
@property1 = "value1,val'=value2  "
@property2 = "value2 '[property2'=this is not an object']"
@property3 = 
   AnotherObjectType
   @property4 = "some value4"

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