在C#中使用正则expression式查找带引号的引号string

我试图在一行中find所有引用的文本。

例:

"Some Text" "Some more Text" "Even more text about \"this text\"" 

我需要得到:

  • "Some Text"
  • "Some more Text"
  • "Even more text about \"this text\""

\"[^\"\r]*\"除了最后一个外,还给我提供了一切,因为引号已经被删除了。

我已经阅读了\"[^\"\\]*(?:\\.[^\"\\]*)*\" working,但在运行时出现错误:

 parsing ""[^"\]*(?:\.[^"\]*)*"" - Unterminated [] set. 

我该如何解决?

你在那里有一个Friedl的“展开循环”技术的例子,但是你似乎对如何把它表示成一个string文字有些困惑。 以下是它应该看到的正则expression式编译器:

 "[^"\\]*(?:\\.[^"\\]*)*" 

最初的"[^"\\]*匹配一个引号,后跟零或多个除引号或反斜杠之外的任何字符。 单独的部分,以及最后的" ,将匹配没有embedded转义序列的简单引用的string,如"this"""

如果确实遇到反斜杠\\. 消耗反斜杠及其后的任何内容, [^"\\]* (再次)消耗一切,直到下一个反斜杠或引号为止。该部分会根据需要重复多次,直到出现非引号引号string的结尾和匹配尝试失败)。

请注意,这将匹配"foo\"-\"foo\"-"bar" 。 这看起来似乎揭示了正则expression式中的一个缺陷,但事实并非如此。 这是无效的input 。 目标是匹配引用的string,可选地包含反斜线引号,embedded到其他文本中 – 为什么会在引用的string之外进行转义引号? 如果你确实需要支持这个,那么你的问题就更复杂了,需要一个非常不同的方法。

正如我所说,以上是如何正则expression式应该看正则expression式编译器。 但是你是以string的forms来写的,而且这些字符往往会特别对待某些字符 – 也就是反斜杠和引号。 幸运的是,C#的逐字string为您省去了双反斜杠的麻烦。 你只需要用另一个引号将每个引号转义出来:

 Regex r = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*"""); 

所以规则是C#编译器的双引号和正则expression式编译器的双反斜杠 – 很好很容易。 这个特定的正则expression式可能看起来有点尴尬,用三个引号在任何一端,但考虑替代:

 Regex r = new Regex("\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\""); 

在Java中,你总是必须这样写。 🙁

用于捕获string的正则expression式(用于字符转义),用于.NET引擎:

 (?>(?(STR)(?(ESC).(?<-ESC>)|\\(?<ESC>))|(?!))|(?(STR)"(?<-STR>)|"(?<STR>))|(?(STR).|(?!)))+ 

在这里,一个“友好”的版本:

 (?> | especify nonbacktracking (?(STR) | if (STRING MODE) then (?(ESC) | if (ESCAPE MODE) then .(?<-ESC>) | match any char and exits escape mode (pop ESC) | | else \\(?<ESC>) | match '\' and enters escape mode (push ESC) ) | endif | | else (?!) | do nothing (NOP) ) | endif | | -- OR (?(STR) | if (STRING MODE) then "(?<-STR>) | match '"' and exits string mode (pop STR) | | else "(?<STR>) | match '"' and enters string mode (push STR) ) | endif | | -- OR (?(STR) | if (STRING MODE) then . | matches any character | | else (?!) | do nothing (NOP) ) | endif )+ | REPEATS FOR EVERY CHARACTER 

基于http://tomkaminski.com/conditional-constructs-net-regular-expressions的例子。; 它依靠引号平衡。 我用它取得了巨大的成功。 与Singleline标志一起使用。

要使用正则expression式,我推荐Rad Software Regular Expression Designer ,它有一个很好的“语言元素”选项卡,可以快速访问一些基本的指令。 它基于.NET的正则expression式引擎。

 "(\\"|\\\\|[^"\\])*" 

应该工作。 匹配转义引号,转义反斜线或除引号或反斜线字符以外的其他任何字符。 重复。

在C#中:

 StringCollection resultList = new StringCollection(); Regex regexObj = new Regex(@"""(\\""|\\\\|[^""\\])*"""); Match matchResult = regexObj.Match(subjectString); while (matchResult.Success) { resultList.Add(matchResult.Value); matchResult = matchResult.NextMatch(); } 

编辑:增加了反斜杠到列表正确处理"This is a test\\"

说明:

首先匹配一个引号字符。

然后,从左到右评估替代scheme。 引擎首先尝试匹配转义报价。 如果不匹配,它会尝试转义反斜杠。 这样,就可以区分"Hello \" string continues""String ends here \\"

如果两者不匹配,则除了引号或反斜线字符之外的其他内容都是允许的。 然后重复。

最后,匹配结束报价。

我build议得到RegexBuddy 。 它可以让你玩弄它,直到你确定testing集中的所有东西都匹配。

至于你的问题,我会尝试四个而不是两个:

 \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\" 

正则expression式

 (?<!\\)".*?(?<!\\)" 

也将处理以逃脱引用开头的文本:

 \"Some Text\" Some Text "Some Text", and "Some more Text" an""d "Even more text about \"this text\"" 

我知道这不是最干净的方法,但用你的例子,我会检查字符之前的"看,如果它是\ 。如果是,我会忽略报价。

与@Blankasaurus发布的RegexBuddy类似, RegexMagic也可以帮助你。

一个简单的答案,没有使用?

 "([^\\"]*(\\")*)*\" 

或者,作为一个逐字串

 @"^""([^\\""]*(\\"")*(\\[^""])*)*""" 

这只是意味着:

  • find第一个"
  • find任何不是\"
  • find任何数量的转义报价\"
  • find任何数量的不是引号的转义字符
  • 重复最后三个命令,直到find"

我相信它的效果和@Alan Moore的答案一样好,但是对我来说,更容易理解。 它接受无与伦比的(“不平衡”)报价。

那么,艾伦·摩尔(Alan Moore)的答案是好的,但我会稍微修改一下,以使其更加紧凑。 对于正则expression式编译器:

 "([^"\\]*(\\.)*)*" 

与艾伦·摩尔的表述相比较:

 "[^"\\]*(\\.[^"\\]*)*" 

这个解释和Alan Moore的解释非常相似:

第一部分"匹配一个引号。

第二部分[^"\\]*匹配除引号或反斜杠以外的零个或多个任意字符。

最后一部分(\\.)*匹配反斜杠,任何单个字符都跟随它。 注意*,说这个组是可选的。

所描述的部分以及最后的" (即"[^"\\]*(\\.)*" )将匹配:“一些文本”和“甚至更多文本”,但不匹配:“甚至更多关于“这个文本”的文字。

为了使它成为可能,我们需要部分: [^"\\]*(\\.)*根据需要重复多次,直到一个非转义的引号出现(或者到达string的末尾并且匹配尝试所以我用方括号把这个部分包起来,加了一个星号,现在匹配:“一些文本”,“甚至更多的文本”,“甚至更多关于\”这个文本\“和”你\“的文本。 。

在C#代码中,它将如下所示:

 var r = new Regex("\"([^\"\\\\]*(\\\\.)*)*\""); 

顺便说一下,这两个主要部分的顺序: [^"\\]*(\\.)*无关紧要。您可以写:

 "([^"\\]*(\\.)*)*" 

要么

 "((\\.)*[^"\\]*)*" 

结果将是一样的。

现在我们需要解决另一个问题: \"foo\"-"bar" 。 当前的expression式会匹配到"foo\"-" ,但是我们想把它匹配到"bar" ,我不知道

为什么引用的string之外会有逃脱的引号?

但是我们可以通过在开始处添加以下部分来轻松实现: (\G|[^\\]) 。 它表示我们希望比赛在上一场比赛结束的时候开始,或者在除了反斜线之外的任何字符之后。 为什么我们需要\G ? 这是针对以下情况,例如: "a""b"

请注意, (\G|[^\\])"([^"\\]*(\\.)*)*"\"foo\"-"bar" -"bar"匹配。只有"bar" ,我们需要指定组,并可select给它一个名称,例如“我的组”,然后C#代码将如下所示:

 [TestMethod] public void RegExTest() { //Regex compiler: (?:\G|[^\\])(?<MyGroup>"(?:[^"\\]*(?:\.)*)*") string pattern = "(?:\\G|[^\\\\])(?<MyGroup>\"(?:[^\"\\\\]*(?:\\\\.)*)*\")"; var r = new Regex(pattern, RegexOptions.IgnoreCase); //Human readable form: "Some Text" and "Even more Text\"" "Even more text about \"this text\"" "Hello\\" \"foo\" - "bar" "a" "b" c "d" string inputWithQuotedText = "\"Some Text\" and \"Even more Text\\\"\" \"Even more text about \\\"this text\\\"\" \"Hello\\\\\" \\\"foo\\\"-\"bar\" \"a\"\"b\"c\"d\""; var quotedList = new List<string>(); for (Match m = r.Match(inputWithQuotedText); m.Success; m = m.NextMatch()) quotedList.Add(m.Groups["MyGroup"].Value); Assert.AreEqual(8, quotedList.Count); Assert.AreEqual("\"Some Text\"", quotedList[0]); Assert.AreEqual("\"Even more Text\\\"\"", quotedList[1]); Assert.AreEqual("\"Even more text about \\\"this text\\\"\"", quotedList[2]); Assert.AreEqual("\"Hello\\\\\"", quotedList[3]); Assert.AreEqual("\"bar\"", quotedList[4]); Assert.AreEqual("\"a\"", quotedList[5]); Assert.AreEqual("\"b\"", quotedList[6]); Assert.AreEqual("\"d\"", quotedList[7]); } 

任何你需要做的机会: \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"