【问题标题】:Finding quoted strings with escaped quotes in C# using a regular expression使用正则表达式在 C# 中使用转义引号查找带引号的字符串
【发布时间】:2023-04-10 22:43:01
【问题描述】:

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

例子:

"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.

我该如何解决这个问题?

【问题讨论】:

    标签: c# regex quotes escaping


    【解决方案1】:

    您所获得的是 Friedl 的“展开循环”技术的示例,但您似乎对如何将其表示为字符串文字有些困惑。以下是正则表达式编译器的外观:

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

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

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

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

    正如我所说,以上是正则表达式在正则表达式编译器中的外观。但是您以字符串文字的形式编写它,并且那些倾向于特殊处理某些字符 - 即反斜杠和引号。幸运的是,C# 的逐字字符串为您省去了双转义反斜杠的麻烦;你只需要用另一个引号转义每个引号:

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

    所以规则是 C# 编译器的双引号和正则表达式编译器的双反斜杠 - 很好而且简单。这个特殊的正则表达式可能看起来有点尴尬,两端都有三个引号,但考虑一下替代方案:

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

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

    【讨论】:

    • 我最喜欢这个解释。
    • 浏览一些让您出名的答案... 为这个最糟糕的反斜杠汤做出如此清晰的解释而点赞! :)
    • 单引号 (') 可以达到同样的效果
    • @KalpeshRajai:当然,只需在我的第一个正则表达式中将双引号替换为单引号即可。你甚至不需要转义它们(除非你使用的是单引号字符串,C# 不支持)。
    • @AlanMoore:谢谢
    【解决方案2】:

    用于捕获字符串的正则表达式(\ 用于字符转义),用于 .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 的正则表达式引擎。

    【讨论】:

      【解决方案3】:
      "(\\"|\\\\|[^"\\])*"
      

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

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

      如果其中一个不匹配,则允许使用除引号或反斜杠字符之外的任何其他字符。然后重复。

      最后,匹配结束引号。

      【讨论】:

      • 很抱歉编辑了这篇文章。但现在我认为我已经足够优雅了。也是正确的。我希望。
      • 此正则表达式不适用于此文本:\"Some Text\" Some Text "Some Text" 和 "Some more Text" an""d "Even more text about \"this text\" "
      • 这太棒了!我认为问题的一部分是我没有使用@,这增加了更多的复杂性,不得不在整个地方进行斜线。
      • 好吧,用转义引号括起来的文本不是问题的一部分;两者都不是加倍作为转义引号的另一种方式。
      • 再次抱歉,但是:"(\\"|\\\\|[^"\\])*" 不匹配:"\n" 或 "\t"。这里需要的模式是:"([^"\\]|\\.)*" 匹配正确(或者更好的是:"([^"\\]++|\\.)*",如果所有格量词可用)。但是 Friedl 的这个表达式的展开版本要快得多。见艾伦的回答。你读过MRE3 了吗?如果没有,我知道您会非常喜欢它(如果您喜欢正则表达式 - 我认为您是)。
      【解决方案4】:

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

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

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

      【讨论】:

      • RegexBuddy 的卖点之一是它可以自动将正则表达式转换为您指定的任何语言的源代码。在这种情况下,它将“原始”正则表达式 "[^"\\]*(?:\\.[^"\\]*)*" 转换为 @"""[^""\\]*(?:\\.[^""\\]*)*"""。
      【解决方案5】:

      正则表达式

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

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

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

      【讨论】:

      • 有没有一种方法可以适用于多行引用的字符串?
      • 这不处理字符串末尾的转义反斜杠:"Hello\\"。
      【解决方案6】:

      嗯,Alan Moore 的回答很好,但我会对其进行一些修改以使其更紧凑。对于正则表达式编译器:

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

      对比艾伦·摩尔的表情:

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

      解释和艾伦摩尔的解释很相似:

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

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

      最后一部分(\\.)* 匹配反斜杠及其后面的任何单个字符。注意*,表示该组是可选的。

      所描述的部分以及最后的"(即"[^"\\]*(\\.)*")将匹配:“Some Text”和“Even more Text\”,但不会匹配:“Even more text about \”this文本\""。

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

      在 C# 代码中,它看起来像:

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

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

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

      或

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

      结果是一样的。

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

      为什么在引用的字符串之外会有转义的引号

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

      请注意,(\G|[^\\])"([^"\\]*(\\.)*)*" 与 \"foo\"-"bar" 中的 -"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]);
      }
      

      【讨论】:

      • 虽然我很欣赏这个正则表达式的努力,但它明显比 Alan 的原版慢。
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          一个简单的答案,不使用?,是

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

          或者,作为逐字字符串

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

          意思是:

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

          我相信它和@Alan Moore 的回答一样好,但对我来说,更容易理解。它也接受不匹配(“不平衡”)的引号。

          【讨论】:

          【解决方案10】:

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

          【讨论】:

          • 这给了我:“一些文本”; “更多文字”; ""
          【解决方案11】:

          如果你可以定义开始和结束,以下应该可以工作:

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

          【讨论】:

            猜你喜欢
            • 2015-01-22
            • 2010-09-19
            • 2020-01-11
            • 1970-01-01
            • 1970-01-01
            • 2011-12-04
            • 2011-05-03
            • 2014-05-12
            • 2013-10-01
            相关资源
            最近更新 更多