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

时间:2013-01-02 22:08:59

标签: c# regex

我正在解析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))");

要匹配分隔符,请使用逗号后跟空格,单词字符后跟文字前面的空格,以及逗号和空格前的文字(不包括=符号)。

当我得到比赛而不是分组时,我得到了这个:

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

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

谢谢, 菲尔

2 个答案:

答案 0 :(得分:0)

你可以使用两个正则表达式和一个带有一个警告的递归函数来执行此操作:必须转义特殊字符。从我所看到的情况来看,"=""[""]"具有特殊含义,因此如果您希望它们作为您的一部分出现,则必须在这些字符前插入"\"适当的价值。请注意,逗号不被视为“特殊”。忽略"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的值是应该被视为文字字符串还是复杂类型?

答案 1 :(得分:0)

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

对于C#,GPLEXGPPG似乎是一些好的。请参阅here for why you might want to use them

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

这就是这些工具的工作原理。文档非常简短,所以通过所有来阅读,预先花时间会有很大帮助。

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

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

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