当分隔符可以在标记中时,使用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中缺少的信息。更复杂的输入有时会在令牌中包含右括号,如:我不知道为什么会这样。有什么改进的办法吗?
谢谢,菲尔。
使用词法分析器和解析器工具最容易回答这个问题。许多人认为它们对于这些"简单"的用例来说太复杂了,尽管我总是发现它们更清晰,更容易推理。你不会陷入愚蠢的逻辑。
对于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"
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的值应该被视为字面值字符串还是复杂类型?