Bỏ qua để đến nội dung

Bộ phân giải CSS: thác đổ và độ ưu tiên

Lớp CssResolver khớp bộ chọn với luồng token, sắp xếp các quy tắc đã khớp theo lớp thác đổ, độ ưu tiên và thứ tự tài liệu, rồi áp dụng !important ở lượt thứ hai.

Terminal window
composer require nextpdf/core:^3

CssResolver là thành phần Layer 1 (theo ADR-010). Nó quản lý các quy tắc Cascading Style Sheets (CSS) đã được phân tích cú pháp và quyết định khai báo nào áp dụng cho từng phần tử. Lớp này được tách khỏi HtmlParser để giữ cấu trúc rõ ràng, và mang tính nội bộ chứ không phải là một application programming interface (API) công khai.

Bộ phân giải không cần cây tài liệu. Việc khớp bộ chọn đọc luồng token phẳng và dùng các bản đồ chỉ mục do HtmlChildScanner tạo ở Stage 3 của pipeline: số lượng phần tử con, số lượng thẻ cùng loại và trạng thái rỗng. Các bản đồ đó cung cấp dữ liệu cho các lớp giả cấu trúc. Bộ chọn quan hệ :has() sử dụng bước quét trước có giới hạn được mô tả trong các ràng buộc luồng.

Việc phân giải thác đổ chạy theo hai lượt bên trong CssResolver::resolveMatchingProperties(). Lượt 1 áp dụng các khai báo thông thường theo thứ tự thác đổ: trọng số lớp thác đổ trước, sau đó đến độ ưu tiên, rồi đến thứ tự tài liệu. Lượt 2 áp dụng các khai báo !important theo thứ tự độ ưu tiên. Một khai báo !important ghi đè mọi khai báo thông thường, bất kể độ ưu tiên. Cách tách thành hai lượt này là chiến lược triển khai và tạo ra tập thuộc tính đã phân giải để lớp bố cục sử dụng.

Thứ tự thác đổ mà bộ phân giải triển khai phù hợp với đặc tả CSS Cascading and Inheritance của World Wide Web Consortium (W3C). Các khai báo trước hết được sắp xếp theo nguồn gốc và mức quan trọng, sau đó theo độ ưu tiên của bộ chọn. Khi độ ưu tiên bằng nhau, khai báo xuất hiện sau cùng trong thứ tự tài liệu sẽ thắng (CSS Cascade 5 §6.4; xem Tuân thủ). Chú thích trong mã nguồn của CssResolver cũng trích dẫn điều khoản này, vì vậy ngoài đặc tả và bảng thuật ngữ, bạn có thêm một điểm kiểm chứng hành vi.

Độ ưu tiên được tính dưới dạng bộ ba (A, B, C) từ số lượng thành phần ID, class và type; các bộ ba được so sánh theo từng thành phần (Selectors Level 4 §16). NextPDF tính độ ưu tiên cho từng quy tắc đã khớp trước khi sắp xếp thác đổ.

Có một ràng buộc đáng lưu ý. Quy tắc đảo lớp §6.4.3 áp dụng cho các khai báo !important trên các lớp thác đổ, và mã nguồn ghi nhận phần này là chưa hoàn tất trong cụm công việc lớp thác đổ. Khi có khai báo lớp thác đổ và !important đi qua các lớp, thứ tự đã phân giải có thể khác với hành vi đầy đủ theo đặc tả. Ma trận hỗ trợ CSS là nguồn tham chiếu chính thức cho trạng thái hỗ trợ của từng tính năng; trang này không lặp lại mức hỗ trợ theo từng thuộc tính.

Ký hiệuVị tríVai trò
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): voidsrc/Html/CssResolver.phpPhân tích cú pháp khối <style> thành các quy tắc.
CssResolver::resolveMatchingProperties(...)src/Html/CssResolver.phpKhớp các bộ chọn và phân giải thác đổ hai lượt.
CssResolver::resolveHasSelectors(array $tokens): arraysrc/Html/CssResolver.phpBước quét trước :has() có giới hạn (được kiểm soát).
CssResolver::resolveFirstLetterProperties(...)src/Html/CssResolver.phpPhân giải các thuộc tính ::first-letter.
CssResolver::resolvePseudoElementProperties(...)src/Html/CssResolver.phpPhân giải các thuộc tính ::before / ::after.
CssResolver::getLayerRegistry(): LayerRegistrysrc/Html/CssResolver.phpCác lớp thác đổ đã khai báo.

Bạn không gọi bộ phân giải trực tiếp. Bạn viết CSS, và bộ phân giải chạy bên trong writeHtml(). Trong thác đổ bên dưới, p được phân giải thành màu đỏ vì quy tắc class có độ ưu tiên cao hơn quy tắc 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');

Ví dụ này minh họa lượt thứ hai của !important. Khai báo type !important ghi đè khai báo class tương đương inline, ngay cả khi bộ chọn class có độ ưu tiên cao hơn.

<?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 bỏ qua độ ưu tiên. Lượt 2 áp dụng các khai báo !important theo thứ tự độ ưu tiên, và các khai báo đó luôn ghi đè khai báo thông thường.
  • Các lớp thác đổ + !important trên các lớp. Mã nguồn ghi nhận quy tắc đảo lớp §6.4.3 đối với các khai báo important là chưa hoàn tất. Hãy kiểm chứng hành vi bằng ma trận hỗ trợ CSS trước khi dựa vào đó.
  • Không khai báo lớp nào là đường dẫn nhanh. Khi không có @layer, việc sắp thứ tự rút gọn về hành vi chỉ dựa trên độ ưu tiên và giống hệt từng bit với hành vi trước khi có lớp.
  • :has() được kiểm soát. Bước quét trước cho quan hệ chỉ chạy khi tính năng thử nghiệm css.has được bật.
  • Việc khớp bộ chọn dựa trên luồng. Các bộ chọn cấu trúc dùng bản đồ chỉ mục, không duyệt cây. Một bộ chọn cần điều hướng cây tùy ý vượt ra ngoài các bản đồ chỉ mục thì không thể phân giải trong mô hình này.

Trong trường hợp xấu nhất, việc khớp bộ chọn là O(rules × elements), bị giới hạn bởi các giới hạn luồng. Hai phép sắp xếp thác đổ là O(matched rules · log matched rules) cho mỗi phần tử. Đường dẫn không phân lớp bỏ qua hoàn toàn bước phân giải lớp. performance_budget theo từng trang (wall_ms: 1500, peak_mb: 64) đặt ra giới hạn vận hành. Phép đo điểm chuẩn của pipeline kết xuất HTML giúp phòng ngừa hồi quy (công việc đã hợp nhất, PR #564).

Bộ phân giải chỉ thấy CSS được DefaultHtmlSecurityPolicy::isCssPropertyAllowed() cho phép. Danh sách cho phép đặt ra giới hạn bảo mật, còn bảng hỗ trợ thời gian chạy đặt ra một giới hạn năng lực riêng. Một thuộc tính bị chính sách chặn sẽ không bao giờ đến được thác đổ. Xem mô hình bảo mật của module HTML.

Hành viĐặc tảĐiều khoảnreference_id
Sắp xếp thác đổ: origin/importance → độ ưu tiên → thứ tự xuất hiệnW3C CSS Cascading and Inheritance Level 5§6.4 (css_cascade_5#x1.x7.x1.p21)
Độ ưu tiên dưới dạng bộ ba (A,B,C) từ số lượng ID/class/typeW3C Selectors Level 4§16 (selectors_4#x1.x16.p2)
Phân tích cú pháp xác định và khôi phục sau lỗi phân tích cú phápW3C CSS Syntax Level 3§4 (css_syntax_3#x1.x4.p2)

Tài liệu của W3C theo giấy phép CC-BY 4.0. Các phát biểu ở trên đã được diễn giải lại. Các định danh điều khoản và đoạn nội dung được cung cấp để kiểm chứng. NextPDF không tuyên bố tuân thủ đầy đủ các module này; xem ma trận hỗ trợ CSS để biết trạng thái đã kiểm chứng theo từng module.

Năng lực Enterprise. Premium mở rộng tập thuộc tính được khớp và áp dụng. Thuật toán thác đổ và mô hình !important hai lượt là như nhau trên mọi phiên bản. Xem ma trận hỗ trợ CSS.