Salta ai contenuti

Resolver CSS: cascade e specificità

CssResolver individua i selettori corrispondenti nel flusso di token, ordina le regole corrispondenti per cascade layer, specificità e ordine nel documento, quindi applica !important in un secondo passaggio.

Terminal window
composer require nextpdf/core:^3

CssResolver è il componente di Layer 1 (secondo ADR-010). Gestisce le regole CSS analizzate e determina quali dichiarazioni si applicano a ciascun elemento. È stato estratto da HtmlParser per maggiore chiarezza strutturale ed è una classe interna, non un’API pubblica.

Il resolver opera senza un albero del documento. La corrispondenza dei selettori legge il flusso di token piatto e si appoggia anche alle mappe di indice che HtmlChildScanner costruisce nello Stage 3 della pipeline: conteggi dei figli, conteggi per stesso tag e vuotezza. Le pseudo-classi strutturali vengono risolte sulla base di quelle mappe. Il selettore relazionale :has() si appoggia alla pre-scansione limitata descritta nei vincoli di streaming.

La risoluzione della cascade avviene in due passaggi all’interno di CssResolver::resolveMatchingProperties(). Il passaggio 1 applica le dichiarazioni normali secondo l’ordine della cascade: prima il peso del cascade layer, poi la specificità, poi l’ordine nel documento. Il passaggio 2 applica quindi le dichiarazioni !important in ordine di specificità; una dichiarazione !important ha la precedenza su qualsiasi dichiarazione normale, indipendentemente dalla specificità. Questa suddivisione in due passaggi è la strategia di implementazione e produce l’insieme di proprietà risolte che il layer di layout consuma successivamente.

L’ordine della cascade implementato dal resolver è allineato alla specifica W3C CSS Cascading and Inheritance. Le dichiarazioni vengono ordinate prima per origine e importanza, poi per specificità del selettore. A parità di specificità, prevale l’ultima dichiarazione nell’ordine del documento (CSS Cascade 5 §6.4; vedere Conformità). Il commento nel sorgente di CssResolver cita la stessa clausola e costituisce un terzo riscontro per questo comportamento, accanto alla specifica e al glossario.

La specificità viene calcolata come tripla (A, B, C) a partire dai conteggi dei componenti ID, classe e tipo; le triple vengono confrontate componente per componente (Selectors Level 4 §16). NextPDF calcola la specificità per ciascuna regola corrispondente prima dell’ordinamento della cascade.

È importante esplicitare un vincolo. La regola di inversione dei layer §6.4.3 si applica alle dichiarazioni !important tra i cascade layer, e il sorgente la registra come in sospeso per il cluster di lavoro sui cascade layer. Quando sono dichiarati cascade layer e !important attraversa i layer, l’ordine risolto può differire dal comportamento completo previsto dalla specifica. La matrice di supporto CSS è il riferimento autorevole sullo stato di supporto di ciascuna funzionalità, e questa pagina non ripropone il supporto per singola proprietà.

SimboloPosizioneRuolo
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.phpAnalizza un blocco <style> trasformandolo in regole.
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.phpIndividua i selettori corrispondenti e risolve la cascade a due passaggi.
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.phpPre-scansione :has() limitata (con gate).
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.phpRisolve le proprietà ::first-letter.
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.phpRisolve le proprietà ::before / ::after.
CssResolver::getLayerRegistry(): LayerRegistrysrc/Html/CssResolver.phpRestituisce i cascade layer dichiarati.

Il codice chiamante non invoca direttamente il resolver: scrive CSS e il resolver viene eseguito all’interno di writeHtml(). Nella cascade seguente p viene risolto in rosso, perché la regola di classe ha una specificità maggiore della regola di tipo.

<?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');

Dimostra il secondo passaggio di !important. La dichiarazione di classe, equivalente a inline, viene sovrascritta dalla dichiarazione di tipo !important, anche se il selettore di classe ha una specificità maggiore.

<?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 ignora la specificità. Il passaggio 2 applica le dichiarazioni !important in ordine di specificità; queste hanno sempre la precedenza sulle dichiarazioni normali.
  • Cascade layer + !important tra i layer. La regola di inversione dei layer §6.4.3 per le dichiarazioni important è registrata come in sospeso nel sorgente. Verificare il comportamento rispetto alla matrice di supporto CSS prima di farvi affidamento.
  • Nessun layer dichiarato attiva il percorso veloce. In assenza di @layer, l’ordinamento si riduce alla sola specificità e coincide bit per bit con il comportamento precedente ai layer.
  • :has() è soggetto a gate. La pre-scansione relazionale viene eseguita solo quando la funzionalità sperimentale css.has è abilitata.
  • La corrispondenza dei selettori è basata sul flusso. I selettori strutturali usano mappe di indice, non un attraversamento dell’albero. Un selettore che richiederebbe una navigazione arbitraria dell’albero oltre le mappe di indice non può essere risolto in questo modello.

La corrispondenza dei selettori è O(regole × elementi) nel caso peggiore, entro i limiti di streaming. I due ordinamenti della cascade sono O(regole corrispondenti · log regole corrispondenti) per elemento. Il percorso senza layer evita del tutto la risoluzione dei layer. Il performance_budget per pagina (wall_ms: 1500, peak_mb: 64) è il limite operativo. Il benchmark della pipeline di rendering HTML protegge dalle regressioni (lavoro integrato, PR #564).

Il resolver vede solo il CSS ammesso da DefaultHtmlSecurityPolicy::isCssPropertyAllowed(). L’elenco delle proprietà consentite è il limite di sicurezza, mentre la tabella di supporto a runtime è un limite di capacità distinto. Una proprietà bloccata dai criteri non raggiunge mai la cascade. Vedere il modello di sicurezza del modulo HTML.

ComportamentoSpecificaClausolareference_id
Ordinamento della cascade: origin/importance → specificità → ordine di comparsaW3C CSS Cascading and Inheritance Level 5§6.4 (css_cascade_5#x1.x7.x1.p21)
Specificità come tripla (A,B,C) a partire dai conteggi ID/classe/tipoW3C Selectors Level 4§16 (selectors_4#x1.x16.p2)
Analisi deterministica e ripristino dagli errori di analisiW3C CSS Syntax Level 3§4 (css_syntax_3#x1.x4.p2)

Il materiale W3C è soggetto a CC-BY 4.0. Le affermazioni sopra riportate sono parafrasate. Gli identificatori di clausola e di chunk sono forniti a scopo di verifica. NextPDF non rivendica piena conformità a questi moduli — vedere la matrice di supporto CSS per lo stato verificato per singolo modulo.

Funzionalità Enterprise. Premium amplia l’insieme delle proprietà corrispondenti e applicate. L’algoritmo di cascade e il modello !important a due passaggi sono identici in tutte le edizioni. Vedere la matrice di supporto CSS.