【问题标题】:Tokenizing with RegEx when delimiter can be in token当分隔符可以在标记中时使用 RegEx 进行标记
【发布时间】:2012-12-17 06:56:43
【问题描述】:

我正在解析 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
]
]

请注意属性 4 中缺少的信息。更复杂的输入有时会在令牌中包含右括号,如下所示:value4] 我不确定为什么会这样。关于如何改进这一点的任何想法?

谢谢, 菲尔

【问题讨论】:

  • 不要觉得你需要使用单个正则表达式来解决这个问题;如果它更容易解决,那么使用多个正则表达式来解决问题是完全可以的。那是除非性能成为问题,但我更愿意先使其正确。
  • 属性值是否可以包含'='、'['或']'字符而不是嵌套对象?换行符呢?基本上,你确定你可以可靠地解析这种格式吗?
  • 从技术上讲,属性值可以包含所有分隔符,尽管这不太可能。
  • @Patrick - 这是一个愚蠢的问题,但多个正则表达式的方法是什么?
  • 你想试试正则表达式求值器类吗?

标签: c# regex


【解决方案1】:

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

输入

ObjectType
[
    property1=value1,val\=value2   
    property2=value2 \[property2\=this is not an object\], property3=
        AnotherObjectType [property4=some 
value4]]

正则表达式

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

^\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 的值应该被视为文字字符串还是复杂类型?

【讨论】:

    【解决方案2】:

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

    对于 C#,GPLEXGPPG 似乎是一些不错的选择。见here for why you might want to use them

    在您的情况下,您有一个语法,这就是您如何根据上下文定义不同标记之间的交互。此外,您还拥有在您选择的语言和工具链中实现此语法的详细信息。语法相对容易定义,您已经非正式地这样做了。细节是棘手的部分。如果您有一个框架可以读取某种已定义的写出语法位的方式并生成代码来实际执行它,那不是很好吗?

    简而言之,这就是这些工具的工作原理。文档很短,因此请通读所有文档,提前花点时间会大有帮助。

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

    每个匹配的标记都可以附加任意 C# 代码,并且与解析器中的每个规则相同。

    我通常不使用 C#,但我编写了很多词法分析器和解析器。不同语言的原则是相同的。这是解决您问题的最佳解决方案,将在您的整个职业生涯中一次又一次地帮助您。

    【讨论】:

      猜你喜欢
      • 2018-06-01
      • 1970-01-01
      • 2012-10-04
      • 1970-01-01
      • 2017-09-21
      • 2015-08-12
      • 2010-10-14
      • 2013-11-10
      • 2011-02-27
      相关资源
      最近更新 更多