Mozilla Firefox CSS 解析器移植到 C#
支持所有现代 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
包含一个具有所有属性的 CssDeclaration
。CssDeclaration
中的 Data
和 ImportantData
是属性-值对的列表。
CssValue
表示一个 CSS 值,它可以是单个值、列表或键值对列表等。其类型可以通过其 Unit
属性来区分。要获取特定类型的值,请使用 String
、Color
、List
、Uri
等属性。
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
- 您可以直接从 Visual Studio 安装 NuGet 包(请参阅 NuGet.org 上的更详细说明),或使用程序包管理器控制台。
- 您可以从 GitHub 上的源代码 进行构建。要使用该库,您只需将 Alba.CsCss/Alba.CsCss.csproj 包含在您的解决方案中。
需要反馈
我自己只使用了该库的一小部分。可能存在一些错误或缺少的功能(例如,某些值仍然是内部的)。如果您发现该库很有用,请告诉我您打算如何使用该库以及您最需要哪些功能。该库的未来取决于您的反馈。
历史
- 1.0.0.3 (2013-08-25):首次 NuGet 发布。