【问题标题】:Detect Two Consecutive Single Quotes Inside Single Quotes检测单引号内的两个连续单引号
【发布时间】:2015-05-18 15:36:21
【问题描述】:

我正在努力让这个正则表达式模式完全正确,如果有人有更好的选择,我愿意接受正则表达式之外的其他选项。

情况: 我基本上是在寻找针对 C# 中的文本列解析 T-SQL“in”子句。所以,我需要这样的字符串值: "'don''t', 'do', 'anything', 'stupid'"

并将其解释为值列表(稍后我会处理双单引号):

  • "don''t"
  • "do"
  • "anything"
  • "stupid"

我有一个适用于大多数情况的正则表达式,但我正在努力将其推广到可以接受任何字符或组内双引号的程度:(?:')([a-z0-9\s(?:'(?='))]+)(?:')[,\w]*

我对正则表达式相当有经验,但很少(如果有的话)发现需要环顾四周(因此相应地降低我对正则表达式经验的评估)。

所以,换一种说法,我想要取一串以逗号分隔的值,每个值都用单引号括起来,但可以包含双引号,并输出每个这样的值。

编辑 这是我当前正则表达式的一个无效示例(我的问题是我需要处理分组中的所有字符,并在遇到单引号后没有第二个单引号时停止):

"'don''t', 'do?', 'anything!', '#stupid$'"

【问题讨论】:

  • 你不能在输入的任何地方寻找两个连续的单引号吗?如:inputString.Contains("''")
  • 有些人在遇到问题时会想“我知道,我会使用正则表达式”。现在他们有两个问题。
  • 你能举一个你面临的不工作的常见例子
  • @Dusan 我很清楚这句话,因此我注意到我对其他方法持开放态度
  • 只用一个简单的状态机。逐个字符地读取字符串,并跟踪您当前是否在单引号内。额外的好处是,查看您的代码的任何人都不需要破译神秘的正则表达式。

标签: c# .net regex regex-lookarounds


【解决方案1】:

如果您仍在考虑基于正则表达式的解决方案,您可以使用以下正则表达式:

'(?:''|[^'])*'

或@sln 建议的“展开”版本:

'[^']*(?:''[^']*)*'

demo

它相当简单,它捕获双单引号或任何不是单引号的东西。无需使用任何后视或前瞻。它不处理任何逃逸的实体,但我在您的问题中没有看到这个要求。

此外,此正则表达式将返回易于访问和处理的匹配项

var text = "'don''t', 'do', 'anything', 'stupid'";
var re = new Regex(@"'[^']*(?:''[^']*)*'"); // Updated thanks to @sln, previous (@"'(?:''|[^'])*'");
var match_values = re.Matches(text).Cast<Match>().Select(p => p.Value).ToList();

输出:

【讨论】:

  • 请注意,如果你使用展开循环的方法,同样的事情会变成'[^']*(?:''[^']*)*',没有交替。该方法可以应用于该类型的任何形式的交替,并且比交替形式快 2-1/2。
  • 我刚刚在 regexhero 上进行了测试,速度几乎提高了 1.65 倍:每秒 274,965 次迭代(有交替版本)与每秒 455,349 次迭代(无交替)。不错!
【解决方案2】:

如果您想使用 Capture Collection 功能,您可以在一个
单程。

 #   @"""\s*(?:'([^']*(?:''[^']*)*)'\s*(?:,\s*|(?="")))+"""

 "
 \s* 
 (?:
      '
      (                 # (1 start)
           [^']* 
           (?:
                '' [^']* 
           )*
      )                 # (1 end)
      '
      \s* 
      (?:
           , \s* 
        |  (?= " )
      )
 )+
 " 

C#代码:

 string strSrc = "\"'don''t', 'do', 'anything', 'stupid'\"";

 Regex rx = new Regex(@"""\s*(?:'([^']*(?:''[^']*)*)'\s*(?:,\s*|(?="")))+""");
 Match srcMatch = rx.Match(strSrc);
 if (srcMatch.Success)
 {
    CaptureCollection cc = srcMatch.Groups[1].Captures;
    for (int i = 0; i < cc.Count; i++)
       Console.WriteLine("{0} = '{1}'", i, cc[i].Value);
 }

输出:

 0 = 'don''t'
 1 = 'do'
 2 = 'anything'
 3 = 'stupid'
 Press any key to continue . . .

【讨论】:

  • 我认为它没有按预期工作,请参阅this demo
  • @stribizhev - 此相邻文本中没有逗号。 '#stupid$'" 'ff"" 使包含单引号的 field 无效。总是有边界条件,但没有指定。在问题的范围内,我认为它完全符合我的预期。
  • @stribizhev - 在这种情况下,您必须期望转义的分隔符为 ''。例如,它匹配"'don''t', 'do?', 'anything!', '#stupid$''" ''ff"","'''"
  • 我仍然认为您过于复杂。这里没有必要使用 Captures 集合,因为在某些前导和尾随模式中没有重复模式,并且更简单的正则表达式可以捕获 OP 所需的所有内容。
  • @stribizhev - 我不明白你的意思。您可以扩展“过于复杂”吗?而且,没有使用捕获集合的标准,它要么有效,要么无效。如果工作正常,引擎会保持在自身内部并完成它.. 总是更好的方法。
【解决方案3】:

你为什么不分手', '

Regex regex = new Regex(@"'\s*,\s*'");
string[] substrings = regex.Split(str);

然后通过 Trimming 处理多余的单引号

【讨论】:

  • 按逗号分割不会捕获字段数据中包含的逗号。
  • 我的回答是用单引号括起来的逗号分隔
  • 从来没有这样工作过。它会将其一分为二:'here'', ''there' 当它的一个字段时。使用分隔符引号时,您必须解析字段,而不仅仅是分隔符。
  • 它有效。我刚刚测试了它。它只是缺少我添加的@
【解决方案4】:

在我看来,您对问题的思考过度了。带有转义引号的带引号的字符串看起来就像两个没有转义引号的字符串,一个接一个(甚至它们之间没有空格)。

(?:'[^']*')+

当然,您必须删除封闭的引号,但无论如何您可能都必须进行一些后期处理,以取消转义的引号。

另外请注意,我并不是要验证输入或解决可能的错误;例如,我不费心匹配字符串之间的逗号。如果输入格式正确,则此正则表达式应该是您所需要的。

【讨论】:

  • 这样,第一项 'don''t' 将被拆分为 2 个捕获,这使得处理获得的数据变得更加困难。见this demo, Table tab
  • 我没有使用捕获(我已切换到非捕获组以使其更清晰)。这就是为什么我说他必须去掉引号;这种方法不适合通过群组抓取部分内容。
【解决方案5】:

出于可维护性的考虑,我决定不使用正则表达式并遵循使用状态机的建议。这是我实现的关键:

string currentTerm = string.Empty;
State currentState = State.BetweenTerms;
foreach (char c in valueToParse)
{
 switch (currentState)
 {
     // if between terms, only need to do something if we encounter a single quote, signalling to start a new term
     // encloser is client-specified char to look for (e.g. ')
     case State.BetweenTerms:
         if (c == encloser)
         {
             currentState = State.InTerm;
         }
         break;
     case State.InTerm:
         if (c == encloser)
         {
             if (valueToParse.Length > index + 1 && valueToParse[index + 1] == encloser && valueToParse.Length > index + 2)
             {
                 // if next character is also encloser then add it and move on
                 currentTerm += c;
             }
             else if (currentTerm.Length > 0 && currentTerm[currentTerm.Length - 1] != encloser)
             {
                 // on an encloser and didn't just add encloser, so we are done
                 // converterFunc is a client-specified Func<string,T> to return terms in the specified type (to allow for converting to int, for example)
                 yield return converterFunc(currentTerm);
                 currentTerm = string.Empty;
                 currentState = State.BetweenTerms;
             }
         }
         else
         {
             currentTerm += c;
         }
         break;
 }

 index++;
}

if (currentTerm.Length > 0)
{
 yield return converterFunc(currentTerm);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    • 2018-06-13
    • 2016-12-17
    • 1970-01-01
    相关资源
    最近更新 更多