CSS-Resolver: Kaskade und Spezifität
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“CssResolver gleicht Selektoren anhand des Token-Streams ab, ordnet die passenden Regeln nach Cascade-Layer, Spezifität und Dokumentreihenfolge und wendet danach in einem zweiten Durchlauf !important an.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“CssResolver ist die Komponente von Layer 1 (gemäß ADR-010). Sie hält die geparsten CSS-Regeln vor und ermittelt, welche Deklarationen auf welches Element zutreffen. Sie wurde aus HtmlParser herausgelöst, um die Struktur klarer zu trennen. Sie ist eine interne Klasse und nicht Teil der öffentlichen API.
Der Resolver arbeitet ohne Dokumentbaum. Der Selektor-Abgleich liest den flachen Token-Stream und nutzt außerdem die Index-Maps, die HtmlChildScanner in Stage 3 der Pipeline aufbaut: Anzahl der Kinder, Zählwerte gleicher Tags und Leerheitsinformationen. Strukturelle Pseudoklassen werden anhand dieser Maps aufgelöst. Der relationale Selektor :has() stützt sich auf den begrenzten Pre-Scan, der in Streaming-Constraints beschrieben ist.
Die Kaskadenauflösung erfolgt in CssResolver::resolveMatchingProperties() in zwei Durchläufen. Durchlauf 1 wendet die normalen Deklarationen in Kaskadenreihenfolge an: zuerst nach dem Gewicht des Cascade-Layers, dann nach Spezifität und danach nach Dokumentreihenfolge. Durchlauf 2 wendet anschließend die !important-Deklarationen in Spezifitätsreihenfolge an; eine !important-Deklaration überschreibt jede normale Deklaration unabhängig von der Spezifität. Diese Aufteilung in zwei Durchläufe ist die Implementierungsstrategie und erzeugt die aufgelöste Eigenschaftsmenge, die der Layout-Layer anschließend verarbeitet.
Die Kaskadenreihenfolge, die der Resolver umsetzt, deckt sich mit der W3C-Spezifikation „CSS Cascading and Inheritance“. Deklarationen werden zuerst nach Ursprung und Wichtigkeit und anschließend nach der Spezifität des Selektors sortiert. Bei gleicher Spezifität gewinnt die letzte Deklaration in Dokumentreihenfolge (CSS Cascade 5 §6.4; siehe Konformität). Der Kommentar im Quellcode von CssResolver verweist auf dieselbe Klausel und gibt Ihnen damit neben der Spezifikation und dem Glossar einen dritten Belegpfad für dieses Verhalten.
Die Spezifität wird als Tripel (A, B, C) aus der Anzahl der ID-, Klassen- und Typkomponenten berechnet, und die Tripel werden Komponente für Komponente verglichen (Selectors Level 4 §16). NextPDF berechnet die Spezifität pro passender Regel vor der Kaskadensortierung.
Eine Einschränkung sollte klar benannt werden: Die Layer-Inversion-Regel aus §6.4.3 gilt für !important-Deklarationen über Cascade-Layer hinweg; der Quellcode vermerkt sie als offenen Punkt für das Arbeitscluster zu den Cascade-Layern. Wenn Cascade-Layer deklariert sind und !important Layer-Grenzen überschreitet, kann die aufgelöste Reihenfolge vom vollständigen Spezifikationsverhalten abweichen. Die CSS-Support-Matrix ist die maßgebliche Quelle für den Support-Status je Feature; diese Seite wiederholt den Support je Eigenschaft nicht.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Symbol | Speicherort | Rolle |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | Parst einen <style>-Block in Regeln. |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | Gleicht Selektoren ab und löst die Kaskade in zwei Durchläufen auf. |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | Begrenzter Pre-Scan für :has() (gegated). |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | Löst die Eigenschaften für ::first-letter auf. |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | Löst die Eigenschaften für ::before / ::after auf. |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | Deklarierte Cascade-Layer. |
Codebeispiel – Schnellstart
Abschnitt betitelt „Codebeispiel – Schnellstart“Aufrufer verwenden den Resolver nicht direkt; sie schreiben CSS, und der Resolver läuft innerhalb von writeHtml(). Die folgende Kaskade löst p zu Rot auf, weil die Klassenregel eine höhere Spezifität hat als die Typregel.
<?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');Codebeispiel – Produktion
Abschnitt betitelt „Codebeispiel – Produktion“Das Beispiel zeigt den zweiten Durchlauf für !important. Die inline-äquivalente Klassendeklaration wird von der !important-Typdeklaration überschrieben, obwohl der Klassenselektor eine höhere Spezifität hat.
<?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');Randfälle & Fallstricke
Abschnitt betitelt „Randfälle & Fallstricke“!importantignoriert die Spezifität. Durchlauf 2 wendet die!important-Deklarationen in Spezifitätsreihenfolge an, und sie überschreiben immer die normalen Deklarationen.- Cascade-Layer +
!importantüber Layer hinweg. Die Layer-Inversion-Regel aus §6.4.3 für wichtige Deklarationen ist im Quellcode als offen vermerkt. Prüfen Sie das Verhalten gegen die CSS-Support-Matrix, bevor Sie sich darauf verlassen. - Keine deklarierten Layer bedeuten den schnellen Pfad. Ohne
@layerreduziert sich die Ordnung auf reine Spezifität und ist bitgenau identisch mit dem Verhalten vor der Layer-Einführung. :has()ist gegated. Der relationale Pre-Scan läuft nur, wenn das experimentelle Featurecss.hasaktiviert ist.- Der Selektor-Abgleich ist Stream-basiert. Strukturelle Selektoren nutzen Index-Maps, keinen Baumdurchlauf. Ein Selektor, der eine beliebige Baumnavigation jenseits der Index-Maps benötigen würde, ist in diesem Modell nicht auflösbar.
Performance
Abschnitt betitelt „Performance“Der Selektor-Abgleich ist im schlechtesten Fall O(Regeln × Elemente), begrenzt durch die Streaming-Obergrenzen. Die beiden Kaskadensortierungen sind O(passende Regeln · log passende Regeln) pro Element. Der ungelayerte Pfad überspringt die Layer-Auflösung vollständig. Das performance_budget pro Seite (wall_ms: 1500, peak_mb: 64) ist die operative Obergrenze. Regressionen werden durch den Benchmark der HTML-Render-Pipeline abgesichert (zusammengeführt in PR #564).
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“Der Resolver sieht nur das CSS, das DefaultHtmlSecurityPolicy::isCssPropertyAllowed() zulässt. Die Allowlist ist die Sicherheitsobergrenze, und die Laufzeit-Support-Tabelle bildet eine separate Fähigkeitsobergrenze. Eine durch die Policy blockierte Eigenschaft erreicht die Kaskade nie. Siehe das Sicherheitsmodell des HTML-Moduls.
Konformität
Abschnitt betitelt „Konformität“| Verhalten | Spezifikation | Klausel | reference_id |
|---|---|---|---|
| Kaskadensortierung: origin/importance → Spezifität → Reihenfolge des Auftretens | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| Spezifität als Tripel (A,B,C) aus ID-/Klassen-/Typzählungen | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| Deterministisches Parsen und Wiederherstellung nach Parse-Fehlern | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
W3C-Material steht unter CC-BY 4.0. Die Aussagen oben sind paraphrasiert. Klausel- und Chunk-Bezeichner sind zur Verifikation angegeben. NextPDF beansprucht keine vollständige Konformität mit diesen Modulen – siehe die CSS-Support-Matrix für den verifizierten Status je Modul.
Kommerzieller Kontext
Abschnitt betitelt „Kommerzieller Kontext“Enterprise-Fähigkeit. Premium erweitert die Menge der abgeglichenen und angewendeten Eigenschaften. Der Kaskadenalgorithmus und das Zwei-Durchlauf-Modell für
!importantsind in allen Editionen identisch. Siehe die CSS-Support-Matrix.