CSS-resolver: cascade en specificiteit
In een oogopslag
Sectie met titel “In een oogopslag”De CssResolver matcht selectors met de tokenstream, ordent de gematchte regels op cascadelaag, specificiteit en documentvolgorde, en past daarna !important toe in een tweede doorloop.
Installatie
Sectie met titel “Installatie”composer require nextpdf/core:^3Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”CssResolver is de Layer 1-component (volgens ADR-010). Deze beheert de geparseerde regels van Cascading Style Sheets (CSS) en bepaalt welke declaraties op elk element van toepassing zijn. De klasse is uit HtmlParser geëxtraheerd om de structuur overzichtelijk te houden, en is intern in plaats van een publieke application programming interface (API).
De resolver heeft geen documentboom nodig. Het matchen van selectors leest de platte tokenstream en gebruikt de indexkaarten die HtmlChildScanner opbouwt in pipeline Stage 3: het aantal onderliggende elementen, het aantal elementen met dezelfde tag en leegte. Met die kaarten worden structurele pseudo-classes afgehandeld. De relationele :has()-selector gebruikt de begrensde voorscan die wordt beschreven in streaming constraints.
Cascade-resolutie verloopt in twee doorlopen binnen CssResolver::resolveMatchingProperties(). Doorloop 1 past normale declaraties toe in cascadevolgorde: eerst het gewicht van de cascadelaag, dan specificiteit en daarna documentvolgorde. Doorloop 2 past !important-declaraties toe in volgorde van specificiteit. Een !important-declaratie overschrijft elke normale declaratie, ongeacht de specificiteit. Deze opsplitsing in twee doorlopen is de implementatiestrategie en levert de resolved property set op die de lay-outlaag gebruikt.
De cascadevolgorde die de resolver implementeert, sluit aan op de CSS Cascading and Inheritance-specificatie van het World Wide Web Consortium (W3C). Declaraties worden eerst gesorteerd op oorsprong en belangrijkheid, daarna op de specificiteit van de selector. Bij gelijke specificiteit wint de laatste declaratie in documentvolgorde (CSS Cascade 5 §6.4; zie Conformiteit). Het commentaar in de broncode van CssResolver verwijst naar dezelfde clausule, zodat dit gedrag ook via de broncode te verifiëren is, naast de specificatie en de woordenlijst.
Specificiteit wordt berekend als een (A, B, C)-triplet op basis van het aantal ID-, class- en type-componenten, en de triplets worden component voor component vergeleken (Selectors Level 4 §16). NextPDF berekent de specificiteit voor elke gematchte regel voordat het de cascade sorteert.
Eén beperking is belangrijk. De laaginversieregel uit §6.4.3 geldt voor !important-declaraties over cascadelagen heen, en de broncode registreert deze als openstaand voor het werkcluster cascadelagen. Wanneer cascadelagen worden gedeclareerd en !important lagen overschrijdt, kan de resolved volgorde afwijken van het volledige specificatiegedrag. De CSS support matrix is de gezaghebbende bron voor de ondersteuningsstatus per functie, en deze pagina herhaalt de ondersteuning per property niet.
API-oppervlak
Sectie met titel “API-oppervlak”| Symbool | Locatie | Rol |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | Parseert een <style>-blok tot regels. |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | Matcht selectors en lost de tweetraps-cascade op. |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | Begrensde :has()-voorscan (gated). |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | Lost ::first-letter-properties op. |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | Lost ::before-/::after-properties op. |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | Gedeclareerde cascadelagen. |
Codevoorbeeld — Snelstart
Sectie met titel “Codevoorbeeld — Snelstart”Je roept de resolver niet rechtstreeks aan. Je schrijft CSS, en de resolver draait binnen writeHtml(). In de onderstaande cascade wordt p rood, omdat de classregel een hogere specificiteit heeft dan de typeregel.
<?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');Codevoorbeeld — Productie
Sectie met titel “Codevoorbeeld — Productie”Dit voorbeeld toont de tweede doorloop voor !important. De !important-typedeclaratie overschrijft de inline-equivalente classdeclaratie, ook al heeft de classselector een hogere specificiteit.
<?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');Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”!importantnegeert specificiteit. Doorloop 2 past!important-declaraties toe in volgorde van specificiteit, en die declaraties overschrijven normale declaraties altijd.- Cascadelagen +
!importantover lagen heen. De broncode registreert de laaginversieregel uit §6.4.3 voor important-declaraties als openstaand. Controleer het gedrag aan de hand van de CSS support matrix voordat je erop vertrouwt. - Geen gedeclareerde lagen is het snelle pad. Zonder
@layervalt de ordening terug op gedrag dat alleen op specificiteit is gebaseerd en bit-identiek is aan het gedrag van vóór de lagen. :has()is gated. De relationele voorscan draait alleen wanneer de experimentele functiecss.hasis ingeschakeld.- Het matchen van selectors is streamgebaseerd. Structurele selectors gebruiken indexkaarten, geen boomdoorloop. Een selector die willekeurige boomnavigatie buiten de indexkaarten vereist, is in dit model niet oplosbaar.
Prestaties
Sectie met titel “Prestaties”In het slechtste geval is het matchen van selectors O(regels × elementen), begrensd door de streaming caps. De twee cascadesorteringen zijn O(gematchte regels · log gematchte regels) per element. Het pad zonder lagen slaat de laagresolutie volledig over. De performance_budget per pagina (wall_ms: 1500, peak_mb: 64) bepaalt het operationele plafond. De benchmark van de HTML-renderpipeline vangt regressies af (samengevoegd werk, PR #564).
Beveiligingsopmerkingen
Sectie met titel “Beveiligingsopmerkingen”De resolver ziet alleen CSS die DefaultHtmlSecurityPolicy::isCssPropertyAllowed() toelaat. De allowlist bepaalt het beveiligingsplafond, en de runtime-ondersteuningstabel bepaalt een afzonderlijk capaciteitsplafond. Een property die door het beleid wordt geblokkeerd, bereikt de cascade nooit. Zie het beveiligingsmodel van de HTML-module.
Conformiteit
Sectie met titel “Conformiteit”| Gedrag | Specificatie | Clausule | reference_id |
|---|---|---|---|
| Cascadesortering: origin/importance → specificiteit → volgorde van verschijning | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| Specificiteit als een (A,B,C)-triplet op basis van ID-/class-/type-aantallen | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| Deterministisch parseren en herstel van parsefouten | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
W3C-materiaal valt onder CC-BY 4.0. De bovenstaande beweringen zijn geparafraseerd. Clausule- en chunk-identifiers worden ter verificatie verstrekt. NextPDF claimt geen volledige conformiteit met deze modules; zie de CSS support matrix voor de geverifieerde status per module.
Commerciële context
Sectie met titel “Commerciële context”Enterprise-functionaliteit. Premium verbreedt de set gematchte en toegepaste properties. Het cascade-algoritme en het tweetraps-
!important-model zijn identiek voor alle edities. Zie de CSS support matrix.