9

我需要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) }

我需要获取所有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);
}

这是一个疯狂的正则表达式,但它仍然不起作用——它匹配 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);
    }

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

问题:如何从 CSS 文件中提取所有 URL?(我需要解析任何CSS 文件,而不仅仅是上面作为示例提供的那个。请不要为“noimg”或假设单行声明。)

注意这不是“工具推荐”问题,因为任何解决方案都可以,无论是一段代码、对上述解决方案之一的修复、库或其他任何东西;我已经明确定义了我需要的功能。

4

9 回答 9

6

终于得到了Alba.CsCss,我来自 Mozilla Firefox 的 CSS 解析器端口,工作正常。

首先,问题包含两个错误

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

  2. url函数 ( ) 中的未闭合引号url('img)是严重的语法错误;包括 Firefox 在内的网络浏览器似乎无法从中恢复,只是跳过了 CSS 文件的其余部分。因此,要求解析器返回“img9”和“img10”是不必要的(但如果删除了两个有问题的行,则有必要)。

对于 CsCss,有两种解决方案。

一个解决方案是仅依赖 tokenizerCssScanner

List<string> uris = new CssLoader().GetUris(source).ToList();

这将返回所有“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();

如果删除了两个有问题的行,这将返回所有正确的“img”URL。

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

于 2013-08-25T14:11:58.567 回答
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;
    }
}

更新:

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

由于我已经编写了很多解析代码并且有一些时间,所以我决定尝试一下。我写了一个简单的 CSS 解析器并写了一篇关于它的文章。您可以在A Simple CSS Parser阅读文章并下载代码(免费)。

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

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

我认为这是一个很好的开始。我也可以为您编写剩余的代码。但这有什么好玩的。:)

于 2013-08-20T20:29:56.790 回答
2

您可以像这样尝试这种模式有更多帮助

@import ([""'])(?<url>[^""']+)\1|url\(([""']?)(?<url>[^""')]+)\2\)

或者

http://www.c-sharpcorner.com/uploadfile/rahul4_saxena/reading-and-parsing-a-css-file-in-Asp-Net/

于 2013-08-23T09:23:08.213 回答
2

在我看来,您创建了太多复杂的 RegExp。工作的如下:url\s*[(][\s'""]*(?<Url>img[\w]*)[\s'""]*[)]。我将尝试解释我正在搜索的内容:

  1. 从...开始url
  2. 然后它后面的所有空格 ( \s*)
  3. 接下来正好是一个左括号 ( [(])
  4. 0 个或多个字符,例如:空格、"、' ( [\s'""]*)
  5. 接下来是“URL”,以零个或多个字母数字字符 ( )开头img和结尾(?<Url>img[\w]*)
  6. 又是 0 个或多个字符,例如:空格、"、' ( [\s'""]*)
  7. 并以右括号结束[)]

完整的工作代码:

        var source =
            "b { background: url(img0) }\n" +
            "b { background: url(\"img1\") }\n" +
            "b { background: url(\'img2\') }\n" +
            "b { background: url( img3 ) }\n" +
            "b { background: url( \"img4\" ) }\n" +
            "b { background: url( \'img5\' ) }\n" +
            "b { background: url (img6) }\n" +
            "b { background: url (\"img7\") }\n" +
            "b { background: url (\'img8\') }\n" +
            "{ background: url(\'noimg0) }\n" +
            "{ background: url(noimg1\') }\n" +
            "/*b { background: url(noimg2) }*/\n" +
            "b { color: url(noimg3) }\n" +
            "b { content: \'url(noimg4)\' }\n" +
            "@media screen and (max-width: 1280px) { b { background: url(img9) } }\n" +
            "b { background: url(img10) }";


        string strRegex = @"url\s*[(][\s'""]*(?<Url>img[\w]*)[\s'""]*[)]";
        var reUrls = new Regex(strRegex);

        var result = reUrls.Matches(source)
                           .Cast<Match>()
                           .Select(match => match.Groups["Url"].Value).ToArray();
        bool isOk = true;
        for (var i = 0; i <= 10; i++)
        {
            if (!result.Contains("img" + i))
            {
                Console.WriteLine("Missing img"+i);
                isOk = false;
            }
        }
        for (var i = 0; i <= 4; i++)
        {
            if (result.Contains("noimg" + i))
            {
                Console.WriteLine("Redundant noimg" + i);
                isOk = false;
            }
        }
        if (isOk)
        {
            Console.WriteLine("Yes. It is ok :). The result is:");
            foreach (var s in result)
            {
                Console.WriteLine(s);
            }

        }
        Console.ReadLine();
于 2013-08-20T07:46:13.670 回答
1

对于这样的问题,更简单的方法可以解决问题。

  1. 将所有 css 命令分成几行(假设 css 被简化),在这种情况下,我会在“;”中打断 或“}”命令。

  2. 阅读 url(*) 中的所有出现,甚至是错误的。

  3. 使用命令模式创建一个管道,以检测哪些行真正符合条件

    • 3.1 Command1(检测评论)
    • 3.2 Command2(检测语法错误URL)
    • 3.3 ...
  4. 标记 OK 行,提取 OK Url

这是一种简单的方法,可以高效地解决问题,并且没有超复杂的难以管理的神奇正则表达式。

于 2013-08-26T13:48:32.663 回答
1

你需要消极的向后看,看看是否没有像这样/*的以下内容*/

(?<!\/\*([^*]|\*[^\/])*)

这似乎不可读,这意味着:

(?<!-> 在此匹配之前可能不是:

\/\*-> /*(带有转义斜杠)后跟

([^*]-> 任何不是的字符*

|\*[^\/])-> 或一个字符,但 *本身后跟任何不是的字符/

*)-> 这个not a * or a * without a /字符我们可以有 0 个或更多,最后关闭负向后看

并且您需要积极向后看,以查看正在设置的属性是否是接受url()值的 css 属性。例如,如果您只感兴趣background:background-image:这将是整个正则表达式:

(?<!\/\*([^*]|\*[^\/])*)
(?<=background(?:-image)?:\s*)
url\s*\(\s*(('|")?)[^\n'"]+\1\s*\)

由于此版本需要 css 属性background:background-image:在 url() 之前,它不会检测'url(noimg4)'. 您可以使用简单的管道来添加更多可接受的 css 属性:(?<=(?:border-image|background(?:-image)?):\s*)

我使用\1而不是\k<Quote>因为我不熟悉该语法,这意味着您需要 ?: 来不捕获不需要的子组。据我所知,这是可行的。

最后我使用[^\n'"]了实际的 url,因为我从你的评论中了解到 url('img(1)') 应该可以工作,而[^\)]你的 OP 不会解析它。

于 2013-08-24T18:38:00.997 回答
1

可能不是最优雅的解决方案,但似乎可以完成您需要完成的工作。

public static List<string> GetValidUrlsFromCSS(string cssStr)
{
    //Enter properties that can validly contain a URL here (in lowercase):
    List<string> validProperties = new List<string>(new string[] { "background", "background-image" });

    List<string> validUrls = new List<string>();
    //We'll use your regex for extracting the valid URLs
    var reUrls = new Regex(@"(?nx)
        url \s* \( \s*
            (
                (?! ['""] )
                (?<Url> [^\)]+ )
                (?<! ['""] )
                |
                (?<Quote> ['""] )
                (?<Url> .+? )
                \k<Quote>
            )
        \s* \)");
    //First, remove all the comments
    cssStr = Regex.Replace(cssStr, "\\/\\*.*?\\*\\/", String.Empty);
    //Next remove all the the property groups with no selector
    string oldStr;
    do
    {
        oldStr = cssStr;
        cssStr = Regex.Replace(cssStr, "(^|{|})(\\s*{[^}]*})", "$1");
    } while (cssStr != oldStr);
    //Get properties
    var matches = Regex.Matches(cssStr, "({|;)([^:{;]+:[^;}]+)(;|})");
    foreach (Match match in matches)
    {
        string matchVal = match.Groups[2].Value;
        string[] matchArr = matchVal.Split(':');
        if (validProperties.Contains(matchArr[0].Trim().ToLower()))
        {
            //Since this is a valid property, extract the URL (if there is one)
            MatchCollection validUrlCollection = reUrls.Matches(matchVal);
            if (validUrlCollection.Count > 0)
            {
                validUrls.Add(validUrlCollection[0].Groups["Url"].Value);
            }
        }
    }
    return validUrls;
}
于 2013-08-20T15:28:27.563 回答
1

这个RegEx似乎解决了提供的示例:

background: url\s*\(\s*(["'])?\K\w+(?(1)(?=\1)|(?=\s*\)))(?!.*\*/)
于 2013-08-26T18:07:26.410 回答
1

该解决方案可以避免注释,并处理background-image. 它也处理background可以包含诸如background-color,background-position或之类repeat的属性,而background-image. 这就是我添加这些案例的原因:noimg5, img11, img12.

数据:

string subject =
    @"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) }
      b { background: #FFCC66 url('img11') no-repeat }
      b { background-image: url('img12'); }
      b { background-image: #FFCC66 url('noimg5') }";

图案:

避免注释,因为它们首先匹配。如果评论保持打开状态(没有*/,则后面的所有内容都被视为评论(?>\*/|$)

结果存储在命名的 captureurl中。

string pattern = @"
        /\*  (?> [^*] | \*(?!/) )*  (?>\*/|$)  # comments
      |
        (?<=
            background
            (?>
                -image \s* :     # optional '-image'
              |
                \s* :
                (?>              # allowed content before url 
                    \s*
                    [^;{}u\s]+   # all that is not a ; { } u
                    \s           # must be followed by one space at least
                )?
            )

            \s* url \s* \( \s*
            ([""']?)             # optional quote (single or double) in group 1
        )
        (?<url> [^""')\s]+ )     # named capture 'url' with an url inside
        (?=\1\s*\))              # must be followed by group 1 content (optional quote)
              ";
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace;
Match m = Regex.Match(subject, pattern, options);
List<string> urls = new List<string>();
while (m.Success)
{
    string url = m.Groups["url"].ToString();
    if (url!="") {
        urls.Add(url);
        Console.WriteLine(url);
    }
    m = m.NextMatch();
}
于 2013-08-25T12:04:42.053 回答