65.9K
CodeProject 正在变化。 阅读更多。
Home

Mozilla Firefox CSS 解析器移植到 C#

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (31投票s)

2013年8月27日

MPL

6分钟阅读

viewsIcon

54298

downloadIcon

857

支持所有现代 CSS 功能的 C# CSS 解析器。

引言

Alba.CsCss 库使您能够将 CSS 文件解析为详细的对象模型。它已从 Mozilla Firefox 22 的 C++ 代码移植到 C#,并支持所有现代 CSS 功能。

已实现的功能

  • 支持所有现代 CSS 功能,包括媒体查询、flexbox 属性等。也支持 Mozilla 特有的扩展(以 -moz- 为前缀的属性)。
  • 支持两种解析兼容模式:FullStandars(严格遵循标准)和 Quirks(允许一些不正确的表达式,例如,长度单位可以省略)。
  • 属性值被解析为详细的对象模型。例如,简写属性 background 会展开为多个 background-* 属性。其中,background-image 由一个值列表表示(对应于 CSS3 支持的多个背景);每个值可以是渐变函数或 URL;渐变又包含渐变停止点以及大小和角度等各种属性。
  • 错误恢复遵循标准的 CSS 解析规则。如果遇到不支持的功能或无效语法(通常由“CSS 技巧”引起),解析将跳到下一个声明并从那里继续。
  • 详细的错误日志:当遇到语法错误时,会生成警告消息。消息会记录到 TraceSource 并作为事件触发。
  • 可以使用 NuGet 包 安装。调试符号和源文件由 SymbolSource 包 提供。

未实现的功能(这些功能在待办列表中)

  • 不同的编码。@charset 规则被忽略。用户需要在解析前将字符串转换为 Unicode。
  • 修改已解析的样式表并将其序列化回字符串。
  • CSS 对象模型。像所有“标准”DOM 模型一样,CSSOM 笨拙且不符合 C# 编码标准,因此其必要性令人怀疑。
  • 忽略其他浏览器的特定于供应商的功能(以 -webkit--ms--o- 等为前缀的属性)。
  • 该库包含很少的单元测试,并且未经彻底测试。不幸的是,Firefox 的测试是用 JavaScript 编写的,移植到 C# 太困难了。

背景

我实际上需要的是一种方法来从 CSS 文件中提取所有 url() 表达式,以下载它们链接的所有图片。但是,正如您所见,我有点走火入魔,移植了大量的代码。

已经有一个用 C# 编写的 CSS 文件解析库 ExCSS。但是,它在解析某些表达式后就会停止,因此不符合我的需求。它正在被重写以使用 ANTLR,所以我们可能会看到它支持完整的 CSS 功能集。

在选择要移植到 C# 的库时,我有两个选择:Chrome 的代码和 Firefox 的代码。这两个主要的开源浏览器都支持所有现代 CSS 功能,并且随着 CSS 标准的演变而定期更新。Chrome 依赖于语法生成器,因此移植到 C# 需要找到一个具有相似功能集的工具并重写语法和补充代码。另一方面,Firefox 使用手动编写的 LL(1) 解析器。我选择了后者,因为这样依赖性更少:只有纯 C++,没有第三方工具和语法语言。

由于 CSS 不断发展,我需要找到一种方法,在不麻烦的情况下,随着原始代码新版本的发布,使移植保持最新。我选择使用正则表达式处理 T4 代码生成模板中的 C++ 代码,而不是手动重写代码。由于 C++ 和 C# 语言有很多共同之处,大量代码只需要进行少量修改。有些代码需要进行重大修改,例如成员指针和返回引用。幸运的是,这种情况很少。我也很幸运,因为 Mozilla 的编码标准有非常严格的命名规则:参数、字段、常量、静态变量、枚举成员有不同的前缀。类命名规则缺失或不严格遵守,但这只是一个小问题。

显然,并非所有代码都可以(或值得)通过正则表达式进行转换。那些不太可能更改的代码(CSS 值类型、CSS 规则类型)是手动编写的,以及所有没有直接 C# 对等项的代码,例如用于为“未命名”字段分配额外内存的 new 运算符重载或使用 union

这种方法将来是否能简化 Firefox 新版本代码的移植,只有时间会证明。到目前为止,统计数据相当不错。例如,将 nsCSSParser.cpp 中的 10,000 多行代码转换为 CssParser.conv.cs 只需要 400 行正则表达式(4%)。但较小文件的统计数据不那么令人印象深刻。

Using the Code

解析始于创建一个 CssLoader 类的实例,可以选择更改其 Compatibility 属性,然后调用其主要方法 ParseSheet,该方法将表示 CSS 文件内容的 string 解析为 CssStyleSheet 对象。

ParseSheet 方法接受三个参数:CSS 字符串、样式表 URL(用于日志记录)和基本 URL(用于解析带相对 URL 的 url() 表达式)。

CssStyleSheet css = new CssLoader().ParseSheet("h1, h2 { color: #123; }",
    new Uri("http://example.com/sheet.css"), new Uri("http://example.com/"));
Console.WriteLine(css.SheetUri); // http://example.com/sheet.css

CssStyleSheet 对象包含 Rules 属性,其中包含不同类型的 CSS 规则:样式规则、charset 规则、媒体查询规则、keyframes 规则等。在这种情况下,我们对样式规则感兴趣。可以通过使用 OfType<T>() LINQ 方法过滤 Rules 或通过 StyleRules 属性获取它(对于每种规则类型都有这样的快捷方式;这是代码生成的好处之一)。CssStyleRule 包含一个具有所有属性的 CssDeclarationCssDeclaration 中的 DataImportantData 是属性-值对的列表。

CssValue 表示一个 CSS 值,它可以是单个值、列表或键值对列表等。其类型可以通过其 Unit 属性来区分。要获取特定类型的值,请使用 StringColorListUri 等属性。

CssStyleSheet css = new CssLoader().ParseSheet("h1, h2 { color: #123; }",
    new Uri("http://example.com/sheet.css"), new Uri("http://example.com/"));
Console.WriteLine(css.SheetUri); // http://example.com/sheet.css
// Get color property (equivalent code)
Console.WriteLine(css.StyleRules.Single().Declaration
        .Color.Color.R); // 17
Console.WriteLine(css.Rules.OfType<CssStyleRule>().Single().Declaration
        .GetValue(CssProperty.Color).Color.R); // 17
// Get h1 selector
Console.WriteLine(css.StyleRules.Single().SelectorGroups.First().Selectors.Single().Tag);

一个更有用的例子,提取所有 URL

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

此方法仅依赖于词法分析器 CssScanner。它不会检查属性是否正确,它只会查找所有 url() 表达式。

或者,可以在将文件解析为样式表对象后提取 URL。此方法将最接近 Web 浏览器的行为:它将跳过无效属性、无效表达式(如 color: url(a) url(b))等。

CssStyleSheet css = new CssLoader().ParseSheet(source, sheetUri, baseUri);
// Get rules of CssStyleRule type on all levels (including style rules inside media rules)
List<string> uris = css.AllStyleRules
    // Get property-value pairs, both non-important and important (marked with !important)
    .SelectMany(styleRule => styleRule.Declaration.AllData)
    // A property can be a list of values (background-image, for example, contains a list of URLs)
    .SelectMany(prop => prop.Value.Unit == CssUnit.List ? prop.Value.List : new[] { prop.Value })
    // Filter values of CssUrlValue type
    .Where(val => val.Unit == CssUnit.Url)
    // Get unresolved URLs (you can use Uri property to get resolved URLs)
    .Select(val => val.OriginalUri)
    .ToList();

类图

(点击放大)

源文件中图表的名称是 Alba.CsCss/Diagrams/CssStyleSheet.cd

安装

注意:构建项目需要 .NET 3.5 或更高版本。构建完整的解决方案(包括测试和补充项目)需要 .NET 4.5 或更高版本。

PM> Install-Package Alba.CsCss

需要反馈

我自己只使用了该库的一小部分。可能存在一些错误或缺少的功能(例如,某些值仍然是内部的)。如果您发现该库很有用,请告诉我您打算如何使用该库以及您最需要哪些功能。该库的未来取决于您的反馈。

历史

  • 1.0.0.3 (2013-08-25):首次 NuGet 发布。
© . All rights reserved.