我需要从CSS文件中获取所有URL(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*
个网址,但不需要noimg*
个网址(无效的语法或无效的属性或内部评论)。
我尝试过使用旧的正则表达式。经过一些试验和错误,我得到了这个:
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)。此外,每个人都会说使用正则表达式来解析复杂的语法错误。
让我们尝试另一种方法。根据{{3}},唯一可行的选择是this question(其他选项太简单或过时)。有了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);
}
与正则表达式解决方案不同,此解决方案不会列出无效的网址。但它没有列出一些有效的!即,9和10.看起来这是ExCSS,如果不从头开始重写整个库,就无法修复它。 ANTLR重写似乎是known issue with some CSS syntax。
问题:如何从CSS文件中提取所有网址? (我需要解析任何 CSS文件,而不仅仅是上面提供的那个文件。请不要注意“noimg”或假设单行声明。)
N.B。这不是一个“工具推荐”问题,因为任何解决方案都可以,无论是一段代码,上述解决方案之一,图书馆还是其他任何解决方案;我已经明确定义了我需要的功能。
答案 0 :(得分:6)
最后得到Alba.CsCss,我的Mozilla Firefox的CSS解析器端口,正在工作。
首先,该问题包含两个错误:
url (img)
语法不正确,因为CSS语法中url
和(
之间不允许有空格。因此,“img6”,“img7”和“img8”不应作为网址返回。
url
函数(url('img)
)中未公开的引用是一个严重的语法错误; Web浏览器(包括Firefox)似乎无法从中恢复,只是跳过CSS文件的其余部分。因此,要求解析器返回“img9”和“img10”是不必要的(但如果删除了两个有问题的行,则必须这样做。)
使用CsCss,有两种解决方案。
第一个解决方案是rely just on the tokenizer CssScanner
。
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”网址。
(LINQ查询很复杂,因为CSS3中的background-image
属性可以包含URL列表。)
答案 1 :(得分:5)
RegEx是一个非常强大的工具。但是当需要更多的灵活性时,我更愿意只编写一些代码。
因此,对于非RegEx解决方案,我想出了以下内容。请注意,需要更多的工作才能使此代码更通用以处理任何CSS文件。为此,我还会使用我的text parsing helper class。
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)')
等值的技术,例如解析引用的值。
我认为这是一个非常好的开始。我也可以为你编写剩下的代码。但那有什么好玩的呢。 :)
答案 2 :(得分:2)
在我看来,你创建了太复杂的RegExp。
工作人员如下:url\s*[(][\s'""]*(?<Url>img[\w]*)[\s'""]*[)]
。我将尝试解释我在搜索的内容:
url
\s*
)[(]
)[\s'""]*
)img
开头,以零个或多个字母数字字符结尾((?<Url>img[\w]*)
)[\s'""]*
)[)]
完整的工作代码:
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();
答案 3 :(得分:2)
你可以尝试这样的模式,还有更多的帮助
@import ([""'])(?<url>[^""']+)\1|url\(([""']?)(?<url>[^""')]+)\2\)
或者
http://www.c-sharpcorner.com/uploadfile/rahul4_saxena/reading-and-parsing-a-css-file-in-Asp-Net/
答案 4 :(得分: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;
}
答案 5 :(得分:1)
如果没有/*
这样的*/
,你需要负面的反馈,看看是否有(?<!\/\*([^*]|\*[^\/])*)
:
(?<!
这似乎不可读,这意味着:
\/\*
- &gt;在此比赛之前可能不是:
([^*]
- &gt; / *(使用转义斜杠)后跟
*
- &gt;任何不是|\*[^\/])
*
- &gt;或者 /
的字符,但其后面跟着任何不是*)
not a * or a * without a /
- &gt;这个url()
字符中我们可以有0或更多,最后关闭负面的背后隐藏
您需要正面的lookbehind来查看正在设置的属性是否是接受background:
值的css属性。如果您只对background-image:
和(?<!\/\*([^*]|\*[^\/])*)
(?<=background(?:-image)?:\s*)
url\s*\(\s*(('|")?)[^\n'"]+\1\s*\)
感兴趣,那么这将是整个正则表达式:
background:
由于此版本要求在url()之前使用css属性background-image:
或'url(noimg4)'
,因此它不会检测(?<=(?:border-image|background(?:-image)?):\s*)
。您可以使用简单的管道添加更多可接受的css属性:\1
我使用\k<Quote>
而不是[^\n'"]
,因为我不熟悉该语法,这意味着您需要?:不捕获不需要的子组。据我所知,这可行。
最后我使用了[^\)]
作为实际网址,因为我从您的评论中了解到网址(&#39; img(1)&#39;)应该有效并且您的OP赢得了{{1}}&# 39;解析那个。
答案 6 :(得分: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') }";
模式:
避免评论,因为它们首先匹配。如果评论未公开(不包含*/
),那么之后的所有内容都会被视为评论(?>\*/|$)
。
结果存储在指定的捕获url
中。
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();
}
答案 7 :(得分:1)
对于这样的问题,更简单的方法可以解决问题。
打破行中的所有css命令(简化了css),在这种情况下我会打破“;”或“}”命令。
读取url(*)中的所有内容,即使是错误的内容。
使用命令模式创建一个管道,以检测哪些行真正合格
标记好行后,提取确定网址
这是一个简单的方法,解决了效率问题,没有超复杂的无法管理的魔法正则表达式。
答案 8 :(得分:1)
这RegEx似乎解决了提供的示例:
background: url\s*\(\s*(["'])?\K\w+(?(1)(?=\1)|(?=\s*\)))(?!.*\*/)