跳到內容

CSS 解析器:cascade 與 specificity

CssResolver 會使用選擇器比對 token 串流,將符合的規則依 cascade layer、specificity 與文件順序排序,再於第二輪套用 !important

Terminal window
composer require nextpdf/core:^3

CssResolver 是 Layer 1 元件(依 ADR-010)。它擁有剖析後的 CSS 規則,負責 resolve(解析)哪些宣告會套用到每個元素。它是為了讓結構更清晰而從 HtmlParser 抽離出的內部類別,不是公開 API。

解析器不需要 document tree 也能運作。選擇器比對會讀取扁平的 token 串流,也會使用 HtmlChildScannerpipeline Stage 3 建立的 Index(索引)對映:子元素數量、同標籤數量與是否為空。結構性 pseudo-class 即由這些對映提供答案。關係型 :has() 選擇器則仰賴 streaming constraints 一節所述的有界限預掃描。

Cascade 解析在 CssResolver::resolveMatchingProperties() 內分兩輪進行。第一輪依 cascade 順序套用一般宣告:先看 cascade layer 權重,再看 specificity,最後看文件順序。第二輪再依 specificity 順序套用 !important 宣告;無論 specificity 高低,!important 宣告都會覆蓋任何一般宣告。這種兩輪拆分是實作策略,產生的已解析屬性集會交由 layout 層使用。

解析器實作的 cascade 順序與 W3C CSS Cascading and Inheritance 規格一致。宣告會先依來源與重要性排序,再依選擇器 specificity 排序。specificity 相等時,文件順序中最後一個宣告勝出(CSS Cascade 5 §6.4;參見「符合性(Conformance)」一節)。CssResolver 原始碼中的內嵌註解也引用同一條款,因此除了規格與詞彙表之外,還為此行為提供第三條佐證路徑。

Specificity 由 ID、class 與 type 元件的數量算出 (A, B, C) 三元組,這些三元組會逐一比較各個元件(Selectors Level 4 §16)。NextPDF 會在 cascade 排序之前,為每條符合的規則計算 specificity。

有一項限制需要明確說明。§6.4.3 的 layer 反轉規則適用於跨 cascade layer 的 !important 宣告,原始碼將它記為 cascade-layer 工作群集中尚待處理的項目。當宣告了 cascade layer,且 !important 跨越 layer 時,解析出的順序可能與完整規格行為不同。CSS support matrix 才是各功能支援狀態的權威來源,本頁不重述各屬性的支援情況。

符號位置角色
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.php將單一 <style> 區塊剖析成規則。
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.php比對選擇器並解析兩輪 cascade。
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.php有界限的 :has() 預掃描(由 gate 控制)。
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.php解析 ::first-letter 屬性。
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.php解析 ::before / ::after 屬性。
CssResolver::getLayerRegistry(): LayerRegistrysrc/Html/CssResolver.php已宣告的 cascade layer。

呼叫端不會直接呼叫解析器;他們撰寫 CSS,解析器則在 writeHtml() 內部執行。下面這段 cascade 會將 p 解析為紅色,因為 class 規則的 specificity 高於 type 規則。

<?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 的第二輪。即使 class 選擇器的 specificity 較高,等同於 inline 的 class 宣告仍會被帶有 !important 的 type 宣告覆蓋。

<?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 會忽略 specificity。 第二輪依 specificity 順序套用 !important 宣告,且它們一律覆蓋一般宣告。
  • Cascade layer 加上跨 layer 的 !important 針對重要宣告的 §6.4.3 layer 反轉規則,原始碼將其記為尚待處理。在仰賴此行為之前,請先對照 CSS support matrix 驗證其行為。
  • 未宣告任何 layer 是快速路徑。 在沒有 @layer 的情況下,排序會簡化為僅依 specificity,且行為會與引入 layer 之前位元層級完全相同。
  • :has() 受 gate 控制。 關係型預掃描只有在啟用 css.has 這個實驗性功能時才會執行。
  • 選擇器比對以串流為基礎。 結構性選擇器使用索引對映,而非走訪樹狀結構。若某個選擇器需要超出索引對映能力、任意走訪樹狀結構,在此模型下便無法解析。

選擇器比對在最壞情況下為 O(rules × elements),並受 streaming caps 限制。兩次 cascade 排序對每個元素都是 O(matched rules · log matched rules)。無 layer 的路徑會完全略過 layer 解析。每頁的 performance_budgetwall_ms: 1500peak_mb: 64)是運作上的上限。效能退化由 HTML render-pipeline 基準測試把關(已合併的工作,PR #564)。

解析器只會看到 DefaultHtmlSecurityPolicy::isCssPropertyAllowed() 放行的 CSS。允許清單是安全性上限,而執行時支援表則是另一道能力上限。被原則封鎖的屬性永遠不會抵達 cascade。請參見 HTML 模組安全模型

行為規格條款參考 ID
Cascade 排序:origin/importance → specificity → 出現順序W3C CSS Cascading and Inheritance Level 5(CSS 串接與繼承第 5 級規格)§6.4(css_cascade_5#x1.x7.x1.p21
以 ID/class/type 數量算出的 (A,B,C) specificity 三元組W3C Selectors Level 4(選擇器第 4 級規格)§16(selectors_4#x1.x16.p2
確定性剖析與剖析錯誤復原W3C CSS Syntax Level 3(CSS 語法第 3 級規格)§4(css_syntax_3#x1.x4.p2

W3C 素材採用 CC-BY 4.0 授權。上述聲明皆為改寫。條款與 chunk 識別碼供驗證之用。NextPDF 並未聲稱完全符合這些模組 —— 各模組已驗證的狀態,請參見 CSS support matrix

Enterprise 能力。 Premium 擴大了會被比對與套用的屬性集合。cascade 演算法與兩輪 !important 模型,在各版本之間都是一致的。請參見 CSS support matrix