跳转到内容

CSS 解析器:层叠与优先级

CssResolver 基于词元流匹配选择器,按层叠层、优先级和文档顺序对已匹配规则排序,然后在第二轮应用 !important

Terminal window
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 支持矩阵是各项特性支持状态的权威依据,本页不再重述各属性的支持情况。

符号位置作用
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.php<style> 块解析为规则。
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.php匹配选择器并执行两轮层叠解析。
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.php有界 :has() 预扫描(受开关控制)。
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.php解析 ::first-letter 属性。
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.php解析 ::before / ::after 属性。
CssResolver::getLayerRegistry(): LayerRegistrysrc/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_budgetwall_ms: 1500peak_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 支持矩阵