在C#中解析CSS:提取所有URL

Ath*_*ari 9 css c# regex url parsing

我需要url()从CSS文件中获取所有URL(表达式).例如:

b { background: url(img0) }
b { background: url("img1") }
b { background: url('img2') }
b { background: url( img3 ) }
b { background: url( "img4" ) }
b { background: url( 'img5' ) }
b { background: url (img6) }
b { background: url ("img7") }
b { background: url ('img8') }
{ background: url('noimg0) }
{ background: url(noimg1') }
/*b { background: url(noimg2) }*/
b { color: url(noimg3) }
b { content: 'url(noimg4)' }
@media screen and (max-width: 1280px) { b { background: url(img9) } }
b { background: url(img10) }
Run Code Online (Sandbox Code Playgroud)

我需要获取所有img*URL,但不需要noimg*URL(无效语法或无效属性或内部注释).

我尝试过使用旧的正则表达式.经过一些试验和错误,我得到了这个:

private static IEnumerable<string> ParseUrlsRegex (string source)
{
    var reUrls = new Regex(@"(?nx)
        url \s* \( \s*
            (
                (?! ['""] )
                (?<Url> [^\)]+ )
                (?<! ['""] )
                |
                (?<Quote> ['""] )
                (?<Url> .+? )
                \k<Quote>
            )
        \s* \)");
    return reUrls.Matches(source)
        .Cast<Match>()
        .Select(match => match.Groups["Url"].Value);
}
Run Code Online (Sandbox Code Playgroud)

这是一个疯狂的正则表达式,但它仍然无效 - 它匹配3个无效的URL(即2,3和4).此外,每个人都会说使用正则表达式来解析复杂的语法是错误的.

让我们尝试另一种方法.根据这个问题,唯一可行的选择是ExCSS(其他选择太简单或过时).有了ExCSS,我得到了这个:

    private static IEnumerable<string> ParseUrlsExCss (string source)
    {
        var parser = new StylesheetParser();
        parser.Parse(source);
        return parser.Stylesheet.RuleSets
            .SelectMany(i => i.Declarations)
            .SelectMany(i => i.Expression.Terms)
            .Where(i => i.Type == TermType.Url)
            .Select(i => i.Value);
    }
Run Code Online (Sandbox Code Playgroud)

与正则表达式解决方案不同,此解决方案不会列出无效的URL.但它没有列出一些有效的!即,9和10.看起来这是一些CSS语法的已知问题,并且如果不从头开始重写整个库,则无法修复它.ANTLR重写似乎被放弃了.

问题:如何从CSS文件中提取所有URL?(我需要解析任何 CSS文件,而不仅仅是上面提供的一个CSS文件.请不要注意"noimg"或假设单行声明.)

注意这不是一个"工具推荐"问题,因为任何解决方案都可以,无论是一段代码,上述解决方案之一,图书馆还是其他任何东西; 我已经明确定义了我需要的功能.

Ath*_*ari 6

最后得到了Alba.CsCss,我的Mozilla Firefox的CSS解析器端口,正在工作.

首先,这个问题包含两个错误:

  1. url (img)语法不正确,因为CSS语法之间url(CSS语法中不允许使用空格.因此,"img6","img7"和"img8"不应作为URL返回.

  2. urlfunction(url('img))中的unclosed引用是一个严重的语法错误; Web浏览器(包括Firefox)似乎无法从中恢复,只是跳过CSS文件的其余部分.因此,要求解析器返回"img9"和"img10"是不必要的(但如果删除了两个有问题的行,则是必需的).

使用CsCss,有两种解决方案.

一个解决方案是仅依靠tokenizerCssScanner.

List<string> uris = new CssLoader().GetUris(source).ToList();
Run Code Online (Sandbox Code Playgroud)

这将返回所有"img"URL(上面错误#1中提到的除外),但也会包含"noimg3",因为不会检查属性名称.

第二个解决方案是正确的解析CSS文件.这将最接近地模仿浏览器的行为(包括在未关闭的引用之后停止解析).

var css = new CssLoader().ParseSheet(source, SheetUri, BaseUri);
List<string> uris = css.AllStyleRules
    .SelectMany(styleRule => styleRule.Declaration.AllData)
    .SelectMany(prop => prop.Value.Unit == CssUnit.List
        ? prop.Value.List : new[] { prop.Value })
    .Where(value => value.Unit == CssUnit.Url)
    .Select(value => value.OriginalUri)
    .ToList();
Run Code Online (Sandbox Code Playgroud)

如果删除了两个有问题的行,则会返回所有正确的"img"URL.

(LINQ查询很复杂,因为background-imageCSS3中的属性可以包含URL列表.)


Jon*_*ood 5

RegEx是一个非常强大的工具.但是当需要更多的灵活性时,我更愿意只编写一些代码.

因此,对于非RegEx解决方案,我想出了以下内容.请注意,需要更多的工作才能使此代码更通用以处理任何CSS文件.为此,我还将使用我的文本解析助手类.

IEnumerable<string> GetUrls(string css)
{
    char[] trimChars = new char[] { '\'', '"', ' ', '\t', };

    foreach (var line in css.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
    {
        // Extract portion within curly braces (this version assumes all on one line)
        int start = line.IndexOf('{');
        int end = line.IndexOf('}', start + 1);
        if (start < 0 || end < 0)
            continue;
        start++; end--; // Remove braces

        // Get value portion
        start = line.IndexOf(':', start);
        if (start < 0)
            continue;

        // Extract value and trime whitespace and quotes
        string content = line.Substring(start + 1, end - start).Trim(trimChars);

        // Extract URL from url() value
        if (!content.StartsWith("url", StringComparison.InvariantCultureIgnoreCase))
            continue;
        start = content.IndexOf('(');
        end = content.IndexOf(')', start + 1);
        if (start < 0 || end < 0)
            continue;
        start++;
        content = content.Substring(start, end - start).Trim(trimChars);

        if (!content.StartsWith("noimg", StringComparison.InvariantCultureIgnoreCase))
            yield return content;
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:

您似乎要问的内容似乎超出了stackoverflow的简单操作方法的范围.我不相信你会使用正则表达式得到满意的结果.您将需要一些代码来解析您的CSS,并处理随附的所有特殊情况.

因为我写了很多解析代码并且有一点时间,所以我决定稍微玩一下.我写了一个简单的CSS解析器并写了一篇关于它的文章.您可以在A Simple CSS Parser上阅读文章并下载代码(免费).

我的代码解析一块CSS并将信息存储在数据结构中.我的代码分离并存储每个规则的每个属性/值对.但是,从属性值中获取URL仍需要更多工作.您需要从属性值中解析它们.

我最初发布的代码将为您提供如何处理此问题的开始.但如果您想要一个真正强大的解决方案,那么将需要一些更复杂的代码.您可能想看看我的代码来解析CSS.我在该代码中使用了可用于轻松处理值的技术url('img(1)'),例如解析引用的值.

我认为这是一个非常好的开始.我也可以为你编写剩下的代码.但那有什么好玩的呢.:)