【问题标题】:Need to extract text in a C# Regex需要在 C# 正则表达式中提取文本
【发布时间】:2021-08-21 18:35:34
【问题描述】:

我有一个字符串集合,例如Some song [FEAT. John Doe],我正在尝试提取“特色”部分。它可以通过几个不同的词之一来标识"FEAT|FEAT\\.|Featuring",并且可以用括号括起来也可以不括起来。我正在为此使用正则表达式,这就是我到目前为止所得到的:

[TestMethod]
public void ExtractFeaturedPerformers()
{
    IEnumerable<string> titles = new string[]
    {
        "abc [FEAT one two] 123",
        "def(FEAT. three'four) 456",
        "ghi Featuring five",
        "jkl"
    };

    // Must be able to use an arbitrary array of words
    var arrayOfWords = new string[] { "FEAT", "FEAT.", "Featuring" };
    string options = string.Join("|", arrayOfWords.Select(s => Regex.Escape(s)));
    var result = new List<string>();

    foreach(string title in titles)
    {
        var _ = Regex.Match(title, $@"(?<=({options})\s*)(.*?)(?=[\]\)]|$)");
        if (_.Success)
            result.Add(_.Value);
    }

    Assert.AreEqual(3, result.Count());
    Assert.IsTrue(result.Contains("one two"));
    Assert.IsTrue(result.Contains("three'four"));
    Assert.IsTrue(result.Contains("five"));
}

我大部分时间都在工作,但有两个限制。我的主要问题是第二个结果包括.

. three'four

如何将其作为 Regex 的一部分删除,以便我可以接受任意的 options 字符串,而不是稍后将其剥离?处理. 是我主要关心的问题,但我也很感激从结果中删除前导和尾随空格的建议,这样我以后就不必再调用Trim()了。

【问题讨论】:

    标签: c# regex


    【解决方案1】:

    你需要

    (?:FEAT\.?|Featuring)\s*([^])]*)
    

    regex demo

    详情

    • (?:FEAT\.?|Featuring) - FEAT 和可选的 .Featuring
    • \s* - 零个或多个空格
    • ([^])]*) - 正在捕获组 1:除 ]) 之外的零个或多个字符。

    您需要修改 C# 代码以获取第 1 组值。

    这里是full C# demo

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.RegularExpressions;
    
    public class Test
    {
        public static void Main()
        {
            IEnumerable<string> titles = new string[]
            {
                "abc [FEAT one two] 123",
                "def(FEAT. three'four) 456",
                "ghi Featuring five",
                "jkl"
            };
    
            var keys = new List<string> { "FEAT", "FEAT.", "Featuring" };
            keys = keys.OrderByDescending(x => x.Length).ToList();
            var pattern = $@"(?:{string.Join("|", keys.Select(z => Regex.Escape(z)))})\s*([^])]*)";
            Console.WriteLine(pattern);
            
            var result = new List<string>();
            foreach(string title in titles)
            {
                var _ = Regex.Match(title, pattern);
                if (_.Success)
                    result.Add(_.Groups[1].Value);
            }
        
            Console.WriteLine( result.Count()); // Assert.AreEqual(3, result.Count());
            Console.WriteLine( result.Contains("one two") ); //Assert.IsTrue(result.Contains("one two"));
            Console.WriteLine( result.Contains("three'four") ); //Assert.IsTrue(result.Contains("three'four"));
            Console.WriteLine( result.Contains("five") ); // Assert.IsTrue(result.Contains("five"));
        }
    }
    

    输出是

    (?:Featuring|FEAT.|FEAT)\s*([^])]*)
    3
    True
    True
    True
    

    注意正则表达式模式是如何构建的:

    • var keys = new List&lt;string&gt; { "FEAT", "FEAT.", "Featuring" }; 使用搜索短语初始化 keys 字符串列表
    • keys = keys.OrderByDescending(x =&gt; x.Length).ToList(); - 按长度降序对列表中的项目进行排序
    • var pattern = $@"(?:{string.Join("|", keys.Select(z =&gt; Regex.Escape(z)))})\s*([^])]*)"; - 通过将转义的搜索短语放入非捕获组中创建正则表达式模式,其中| 交替运算符(?:Featuring|FEAT\.|FEAT)

    【讨论】:

    • 为什么要放弃 Lookbehind 解决方案?
    • @41686d6564 因为后视中的可选. 很麻烦。由于正则表达式引擎从左到右搜索匹配项,FEAT 之前的位置会更早找到,并且匹配项中仍会出现点。使用消费模式时,可选点将被消费,并且不会出现在 Group 1 值中。如果Feat 部分之后总是有一个空格,(?&lt;=(?:FEAT\.?|Featuring)\s)([^])]*) 可能会起作用。在这些情况下,捕获机制更安全。
    • 啊,我总是忘记 Lookbehinds 的这个警告。谢谢你,维克托。我站得更正。 :)
    • 感谢您的回答 Wiktor。它适用于 FEATFEAT.Featuring。唯一的麻烦是,我需要能够接受任意字符串。
    • @Connell.O'Donnell 你的意思是像string options = string.Join("|", collectionOfWords.Select(s =&gt; Regex.Escape(s)));吗?
    猜你喜欢
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-08
    • 1970-01-01
    相关资源
    最近更新 更多