我想解析一个持久化对象图状态的自定义字符串格式。这是ASP.NET场景,我想在客户端(JavaScript)和服务器(C#)上使用易于使用的东西。
我的格式类似于
{Name1|Value1|Value2|...|ValueN}{Name2|Value1|...}{...}{NameN|...}
在这种格式中,我有3个分隔符,{
,}
和|
。此外,因为在名称/值中可以想到这些字符,所以我使用非常常见的\
定义了转义序列,以便\{
,\}
和\|
都被解释作为他们自己的正常版本,当然\\
是一个反斜杠。一切都很标准。
最初我尝试使用正则表达式来尝试用这样的(?<!\\)\{(.*?)(?<!\\)\}
来解析对象的字符串表示。请注意,\
,{
和}
都保留在正则表达式中。这当然可以正确解析{category|foo\}|bar\{}
之类的内容。但是我意识到它会因{category|foo|bar\\}
之类的东西而失败。
我只花了一分钟时间来尝试这个(?<!(?<!\\)\\)\{(.*?)(?<!(?<!\\)\\)\}
,并且意识到这种方法是不可能的,因为你需要无限数量的负面观察来处理潜在的无限数量的逃逸序列。当然,我不可能有超过一个或两个级别,所以我可能会硬编码。但是,我觉得这是一个很常见的问题,应该有一个定义明确的解决方案。
我的下一个方法是尝试编写一个定义的解析器,我实际扫描输入缓冲区并使用仅前向方法消耗每个字符。我还没有真正完成这个,但它似乎过于复杂,我觉得我必须遗漏一些明显的东西。我的意思是只要我们有计算机语言,我们就有解析器。
所以我的问题是什么是最简单,有效和优雅的方法来解码像这样的输入缓冲区可能的转义序列?
答案 0 :(得分:6)
(?<!\\)(?:\\\\)*\{(.*?(?<!\\)(?:\\\\)*)\}
在此之前, (?<!\\)
会阻止任何\
。
(?:\\\\)*
将允许任意数量的转义\
。
\{
匹配一个左大括号。
(
开始一个捕获组。
.*?
与内容相匹配,包括任何|
。
(?<!\\)
会阻止任何\
。
(?:\\\\)*
将允许任意数量的转义\
。
)
结束捕获组。
\}
匹配右括号。
答案 1 :(得分:1)
这种解析器很容易用最小的状态跟踪来完成。下面花了我几分钟,相当难看,甚至做了一点点错误检查。 :)
可以说,它比复杂的正则表达式更具可读性,尽管前者更简洁。
struct RECORD
{
public string[] Entries;
}
struct FILE
{
public RECORD[] Records;
}
static FILE parseFile(string input)
{
List<RECORD> records = new List<RECORD>();
List<string> entries = new List<string>();
bool escaped = false;
bool inRecord = false;
StringBuilder sb = new StringBuilder();
foreach (char c in input)
{
switch (c)
{
case '|':
if (escaped)
{
sb.Append('|');
escaped = false;
}
else if (inRecord)
{
entries.Add(sb.ToString());
sb = new StringBuilder();
}
else
throw new Exception("Invalid sequence");
break;
case '{':
if (escaped)
{
sb.Append('{');
escaped = false;
}
else if (inRecord)
throw new Exception("Invalid sequence");
else
{
inRecord = true;
sb = new StringBuilder();
}
break;
case '}':
if (escaped)
{
sb.Append('}');
escaped = false;
}
else if (inRecord)
{
inRecord = false;
entries.Add(sb.ToString());
sb = new StringBuilder();
records.Add(new RECORD(){Entries = entries.ToArray()});
entries.Clear();
}
else
throw new Exception("Invalid sequence");
break;
case '\\':
if (escaped)
{
sb.Append('\\');
escaped = false;
}
else if (!inRecord)
throw new Exception("Invalid sequence");
else
escaped = true;
break;
default:
if (escaped)
throw new Exception("Unrecognized escape sequence");
else
sb.Append(c);
break;
}
}
if (inRecord)
throw new Exception("Invalid sequence");
return new FILE() { Records = records.ToArray() };
}