正则表达式仅匹配一次出现的关键字

RegEx to only match single occurence of a keyword

我很难尝试编写 RegEx 来满足我的特定要求。

这些是:

  1. 匹配关键字并捕获后面的日期
  2. 如果关键字不存在,则什么也不捕获
  3. 如果关键字多次出现,则不捕获任何内容

关键词:

 LT circa

示例文本:

Metall-Notierung 464,95 EUR 100 KG
* LT circa 21.04.2020 2 x 500 M Einwegtrommel 400x 150x 404mm
* LT circa 17.05.2020 2 x 500 M Einwegtrommel 400x 150x 404mm
Zolltarifnummer 80464995

预期结果:

NULL

示例文本:

Metall-Notierung 464,95 EUR 100 KG
* LT circa 17.05.2020 2 x 500 M Einwegtrommel 400x 150x 404mm
Zolltarifnummer 80464995

预期结果:

17.05.2020

作为正则表达式的新手,这些是我迄今为止在简化主题上尝试过的事情:

This test is a test and nothing else

(.*test.*test.*)?(?(1)(a^):(test.*))

...如您所料,如果认为这可行就太天真了。

有专家吗?

编辑:

我使用 .NET Framework 4.7.2 和 NUnit 进行了检查

using NUnit.Framework;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;

namespace Test.RegExpressions.Tests
{
   [TestFixture]
   public class SpecialRegexTests
   {
      [TestCaseSource(typeof(TestCaseClass), nameof(TestCaseClass.TestCases))]
      public int MatchTest(string input, string pattern, RegexOptions regexOptions)
      {
         return new Regex(pattern, regexOptions).Matches(input).Count;
      }
   }

   public static class TestCaseClass
   {
      private static readonly string S0 = new StringBuilder()
         .AppendLine("Metall - Notierung 464,95 EUR 100 KG")
         .AppendLine("* LT circa 21.04.2020 2 x 500 M Einwegtrommel 400x 150x 404mm")
         .AppendLine("* LT circa 17.05.2020 2 x 500 M Einwegtrommel 400x 150x 404mm")
         .AppendLine("Zolltarifnummer 80464995")
         .ToString();

      private static readonly string S1 = new StringBuilder()
         .AppendLine("Metall - Notierung 464,95 EUR 100 KG")
         .AppendLine("* LxT circa 21.04.2020 2 x 500 M Einwegtrommel 400x 150x 404mm")
         .AppendLine("* LT circa 17.05.2020 2 x 500 M Einwegtrommel 400x 150x 404mm")
         .AppendLine("Zolltarifnummer 80464995")
         .ToString();

      private const string R0 = @"^(?:(?!.*LT circa).+\n)*(?:(?!LT circa).)*LT circa\s+(\d\d\.\d\d.\d{4})(?!(?:.+\n)*.*LT circa)";

      private const string R1 = @"(?s)^(?!(?:.*LT circa){2}).*LT circa\s*\K\d{1,2}\.\d{1,2}\.\d{4}";

      private const string R2 = @"(?s)^(?!(?:.*LT circa){2}).*LT circa\s*(\d{1,2}\.\d{1,2}\.\d{4})";

      public static IEnumerable TestCases
      {
         get
         {
            yield return new TestCaseData(S0, R0, RegexOptions.None).Returns(0);
            yield return new TestCaseData(S1, R0, RegexOptions.None).Returns(1);

            yield return new TestCaseData(S0, R1, RegexOptions.None).Returns(0);
            yield return new TestCaseData(S1, R1, RegexOptions.None).Returns(1);
            
            yield return new TestCaseData(S0, R2, RegexOptions.None).Returns(0);
            yield return new TestCaseData(S1, R2, RegexOptions.None).Returns(1);
         }
      }
   }
}

除了使用\KR1外,全部通过测试

一旦我获得有关正在使用的 Regex Flavor 的更多信息,我将立即更新我的问题。

值得一提的是,其中 none 个在软件中有效,这可能是也可能不是我无法控制的 RegEx 选项的问题。

您可以使用

(?s)(?<=^(?!(?:.*LT circa){2}).*LT circa\s*)\d{1,2}\.\d{1,2}\.\d{4}

regex demo。日期正则表达式可以增强,但要点是它周围的模式。

详情:

  • (?s) - s flag making . 匹配任意字符
  • (?<= - 正面回顾的开始:
    • ^ - 字符串开头
    • (?!(?:.*LT circa){2}) - 如果 LT circa 在字符串
    • 中的任意位置出现两次,则匹配失败
    • .* - 尽可能多的任意零个或多个字符
    • LT circa - 关键字
    • \s* - 零个或多个空格
  • ) - 正面回顾结束
  • \d{1,2}\.\d{1,2}\.\d{4} - 日期模式。 (?:0?[1-9]|[12]\d|3[01])\.(?:0?[1-9]|1[0-2])\.\d{4}(?!\d) 可以是任意 dd/MM/yyyy 日期(不支持闰年)的更精确的模式。

您可以用负数 look-aheads 试试这个正则表达式。它稍长但会比使用 DOTALL 模式更有效:

^(?:(?!.*LT circa).+\n)*(?:(?!LT circa).)*LT circa\s+(\d\d\.\d\d.\d{4})(?!(?:.+\n)*.*LT circa)

.NET RegEx Demo