CSS 解析器:层叠与优先级
CssResolver 基于词元流匹配选择器,按层叠层、优先级和文档顺序对已匹配规则排序,然后在第二轮应用 !important。
composer require nextpdf/core:^3概念概述
标题为“概念概述”的章节CssResolver 是第 1 层组件(依据 ADR-010)。它持有已解析的 CSS 规则,并确定哪些声明适用于每个元素。为保持结构清晰,它从 HtmlParser 中拆分出来,作为内部类而非公共 API。
解析器无需文档树即可工作。选择器匹配读取扁平词元流,同时借助 HtmlChildScanner 在流水线第 3 阶段构建的索引映射:子元素计数、同标签计数和空状态。结构性伪类由这些映射判定。关系型 :has() 选择器依赖于流式约束中描述的有界预扫描。
层叠解析在 CssResolver::resolveMatchingProperties() 内部分两轮执行。第 1 轮按层叠顺序应用普通声明:先按层叠层权重,再按优先级,最后按文档顺序。随后第 2 轮按优先级顺序应用 !important 声明;无论优先级如何,!important 声明都会覆盖任何普通声明。这种两轮拆分是一种实现策略,用于生成已解析的属性集,再交由布局层使用。
解析器实现的层叠顺序与 W3C CSS Cascading and Inheritance 规范一致。声明先按来源和重要性排序,再按选择器优先级排序。优先级相同时,文档顺序中最后一条声明胜出(CSS Cascade 5 §6.4;参见“合规性”)。CssResolver 的源代码注释引用了同一条款,因此除规范和术语表之外,还为这一行为提供了第三条证据路径。
优先级根据 ID、类和类型分量的计数计算为一个 (A, B, C) 三元组,并按分量逐一比较(Selectors Level 4 §16)。NextPDF 会在层叠排序之前为每条已匹配规则计算优先级。
有一项约束需要明确说明。§6.4.3 层反转规则适用于跨层叠层的 !important 声明,源代码将其记录为层叠层工作集群中尚待处理的事项。当声明了层叠层且 !important 跨层时,解析后的顺序可能与完整规范行为有所不同。CSS 支持矩阵是各项特性支持状态的权威依据,本页不再重述各属性的支持情况。
API 接口
标题为“API 接口”的章节| 符号 | 位置 | 作用 |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | 将 <style> 块解析为规则。 |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | 匹配选择器并执行两轮层叠解析。 |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | 有界 :has() 预扫描(受开关控制)。 |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | 解析 ::first-letter 属性。 |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | 解析 ::before / ::after 属性。 |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | 已声明的层叠层。 |
代码示例 — 快速开始
标题为“代码示例 — 快速开始”的章节调用方不会直接调用解析器;只需编写 CSS,解析器会在 writeHtml() 内部运行。下面的层叠会将 p 解析为红色,因为类规则的优先级高于类型规则。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: blue; } .lead { color: red; }</style>' . '<p class="lead">Higher-specificity class wins.</p>');$doc->save(__DIR__ . '/output/cascade.pdf');代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节下面演示 !important 的第二轮处理。即使类选择器的优先级更高,对应的类声明也会被 !important 类型声明覆盖。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: green !important; } .lead { color: red; }</style>' . '<p class="lead">!important overrides higher specificity.</p>');$doc->save(__DIR__ . '/output/important.pdf');边界情况与注意事项
标题为“边界情况与注意事项”的章节!important不受普通声明优先级影响。 第 2 轮按优先级顺序应用!important声明;它们始终会覆盖普通声明。- 层叠层 + 跨层的
!important。 针对 important 声明的 §6.4.3 层反转规则在源代码中记录为尚待处理。在依赖该行为之前,请先对照 CSS 支持矩阵验证。 - 未声明层是快速路径。 在没有
@layer时,排序会退化为仅按优先级排序,并且与引入层之前的行为在比特级别上完全一致。 :has()受开关控制。 关系型预扫描仅在启用css.has实验性特性时才运行。- 选择器匹配基于流。 结构性选择器使用索引映射,而非遍历树。需要超出索引映射并进行任意树导航的选择器,在此模型中无法解析。
选择器匹配在最坏情况下为 O(rules × elements),并受流式上限限制。两次层叠排序对每个元素为 O(matched rules · log matched rules)。无层路径会完全跳过层解析。每页的 performance_budget(wall_ms: 1500、peak_mb: 64)是运行时上限。HTML 渲染流水线基准测试用于防止回归(已合并工作,PR #564)。
安全说明
标题为“安全说明”的章节解析器只能看到 DefaultHtmlSecurityPolicy::isCssPropertyAllowed() 放行的 CSS。允许列表是安全上限,运行时支持表则是另一个独立的能力上限。被策略阻止的属性永远不会进入层叠。参见 HTML 模块安全模型。
合规性
标题为“合规性”的章节| 行为 | 规范 | 条款 | reference_id |
|---|---|---|---|
| 层叠排序:origin/importance → 优先级 → 出现顺序 | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| 优先级表示为根据 ID/类/类型计数得出的 (A,B,C) 三元组 | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| 确定性解析与解析错误恢复 | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
W3C 材料采用 CC-BY 4.0 许可。以上陈述均为转述。条款和区块标识符用于核验。NextPDF 并不声称完全符合这些模块——经过验证的各模块状态请参见 CSS 支持矩阵。
商业说明
标题为“商业说明”的章节企业版能力。 Premium 扩大了已匹配和已应用属性的集合。层叠算法和两轮
!important模型在各版本之间完全相同。参见 CSS 支持矩阵。