使用正则表达式在C#中使用转义引号查找带引号的字符串

时间:2010-01-27 16:33:20

标签: c# regex quotes escaping

我试图在一行中找到所有引用的文字。

示例:

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

我需要得到:

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

\"[^\"\r]*\"给了我除了最后一个之外的所有内容,因为有引号转义。

我已阅读有关\"[^\"\\]*(?:\\.[^\"\\]*)*\"正常工作的信息,但我在运行时遇到错误:

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

我该如何解决这个问题?

11 个答案:

答案 0 :(得分:78)

你所拥有的是Friedl的“展开循环”技术的一个例子,但你似乎对如何将它表达为字符串文字有一些困惑。这是它应该如何看待正则表达式编译器:

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

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

如果 遇到反斜杠,\\.会消耗反斜杠及其后的任何内容,而[^"\\]*(再次)会消耗掉下一个反斜杠或引号的所有内容。该部分会根据需要重复多次,直到未转义的引号出现(或者它到达字符串的末尾并且匹配尝试失败)。

请注意,这将与"foo\"-中的\"foo\"-"bar"匹配。这似乎暴露了正则表达式中的一个缺陷,但它没有;这是输入无效。目标是匹配引用的字符串,可选地包含嵌入其他文本的反斜杠转义引号 - 为什么引用字符串的外部转义?如果你真的需要支持它,你就会遇到一个更复杂的问题,需要一种非常不同的方法。

正如我所说,上面是正则表达式应该如何看待正则表达式编译器。但是你是以字符串文字的形式写的,而那些往往特别对待某些字符 - 即反斜杠和引号。幸运的是,C#的逐字字符串为您省去了双重逃避反斜杠的麻烦;你只需要用另一个引号转义每个引号:

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

因此规则是C#编译器的双引号和正则表达式编译器的双反斜杠 - 非常简单。这个特殊的正则表达式可能看起来有点尴尬,两端都有三个引号,但请考虑另一种选择:

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

在Java中,总是必须以这种方式编写它们。 : - (

答案 1 :(得分:12)

用于捕获字符串的正则表达式(使用\进行字符转义),用于.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标志一起使用。

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

答案 2 :(得分:4)

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

应该有效。匹配转义引号,转义反斜杠或除引号或反斜杠字符之外的任何其他字符。重复。

在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\\"

说明:

首先匹配引号字符。

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

如果两者不匹配,则除了引号或反斜杠字符外,还允许其他任何内容。然后重复一遍。

最后,匹配收尾报价。

答案 3 :(得分:3)

我建议RegexBuddy。它可以让你玩它,直到你确保测试集中的所有内容都匹配。

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

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

答案 4 :(得分:2)

正则表达式

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

还将处理以转义引号开头的文本:

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

答案 5 :(得分:1)

我知道这不是最干净的方法,但是在我的例子中,我会在"之前检查字符,看看它是否是\。如果是的话,我会忽略这句话。

答案 6 :(得分:1)

与@Blankasaurus发布的RegexBuddy类似,RegexMagic也有帮助。

答案 7 :(得分:1)

不使用?的简单答案是

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

或作为逐字字符串

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

这只是意味着:

  • 找到第一个"
  • 查找不是\"
  • 的任意数量的字符
  • 找到任意数量的转义引号\"
  • 找到任意数量的转义字符,不是引号
  • 重复最后三个命令,直至找到"

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

答案 8 :(得分:1)

嗯,艾伦摩尔的答案很好,但我会稍微修改它以使其更紧凑。对于正则表达式编译器:

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

与艾伦摩尔的表达相比:

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

解释与艾伦摩尔的解释非常相似:

第一部分"与引号匹配。

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

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

描述的部分以及最终的"(即"[^"\\]*(\\.)*")将匹配:“Some Text”和“Even more Text \”“,但不匹配:”更多文字关于“这篇文章”。“

为了使它成为可能,我们需要部分:[^"\\]*(\\.)*根据需要重复多次,直到未转义的引号出现(或者它到达字符串的末尾并且匹配尝试失败)。所以我用括号括起那个部分并添加了一个星号。现在它匹配:“Some Text”,“更多Text”“,”更多关于\“this text \”“和”Hello \\“的文字。

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

var r = new Regex("\"([^\"\\\\]*(\\\\.)*)*\"");
顺便说一下,两个主要部分的顺序:[^"\\]*(\\.)*并不重要。你可以写:

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

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

结果将是相同的。

现在我们需要解决另一个问题:\"foo\"-"bar"。当前表达式将与"foo\"-"匹配,但我们希望将其与"bar"匹配。我不知道

  

为什么在引用的字符串

之外会有之外的引号

但是我们可以通过在开头添加以下部分来轻松实现它:(\G|[^\\])。它表示我们希望匹配从上一个匹配结束的点开始,或者在除反斜杠之外的任何字符之后。我们为什么需要\G?这适用于以下情况,例如:"a""b"

请注意,(\G|[^\\])"([^"\\]*(\\.)*)*"-"bar"中的\"foo\"-"bar"匹配。因此,要仅获取"bar",我们需要指定该组并可选地为其指定名称,例如“MyGroup”。那么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]);
}

答案 9 :(得分:0)

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

答案 10 :(得分:0)

如果您可以定义开始和结束,则应该可以进行以下操作:

new Regex(@"^(""(.*)*"")$")