Bỏ qua để đến nội dung
getnextpdf.com

Một tệp PDF là một vật chứa: embedded file và dữ liệu liên kết

Spec: ISO 32000-2, §7.11.4Spec: ISO 32000-2, §14.13Spec: ISO 19005-3, PDF/A-3

Hầu hết mọi người hình dung một tệp PDF như một chồng các trang. Đó là phần bạn nhìn thấy. Nhưng một tệp PDF cũng là một vật chứa, và nó có thể mang nguyên các tệp khác bên trong — một bảng tính, một payload XML, tài liệu nguồn gốc — gói lại trong cùng một tệp duy nhất mà bạn trao cho người khác.

Trang này giải thích cách điều đó hoạt động: embedded-file stream lưu các byte, name tree liệt kê chúng, và một khóa duy nhất quyết định xem một tệp đính kèm chỉ đang nằm đó hay thực sự có nghĩa lý gì đó.

Một tệp đính kèm không gán kiểu và một tệp có gán kiểu trông giống hệt nhau dưới mắt con người. Cả hai đều là một tệp đi kèm bên trong một tệp PDF, và — trong engine này — cả hai đều được liên kết với tài liệu. Khác biệt là một trong hai cái nói cho máy biết nó dùng để làm gì, còn cái kia để trống mối quan hệ cho máy phải đoán.

Khác biệt đó quyết định toàn bộ cuộc chơi đối với một hóa đơn điện tử lai. Một nền tảng thuế không đọc trang hóa đơn của bạn; nó đọc cái XML bạn đã nhúng. Nếu cái XML đó được đính kèm như một khối không phân biệt thay vì như dữ liệu hóa đơn cho tài liệu thấy được, thì một trình đọc tuân thủ chẳng có cách nào đáng tin để biết nó là payload cần xử lý. Trang nhìn hoàn hảo. Hóa đơn bị từ chối. Sự cố ập đến vài ngày sau, với một khoản thanh toán bị giữ lại phía sau nó.

Làm đúng mối quan hệ ngay tại tầng tạo ra tệp rẻ hơn nhiều so với việc phát hiện ra nó từng-cái-hóa-đơn-bị-từ-chối một.

  • Một tệp PDF có thể nhúng các byte của bất kỳ tệp nào dưới dạng một embedded file stream (Spec: ISO 32000-2, §7.11.4). Stream mang theo dữ liệu cộng với một parameters dictionary nhỏ: kích thước gốc, các mốc ngày, và một checksum.
  • Các embedded file được lập danh mục trong EmbeddedFiles name tree, nhờ vậy một trình đọc có thể liệt kê chúng theo tên mà không cần quét cả tài liệu.
  • Một associated file đi xa thêm một bước: nó khai báo một AFRelationship (Spec: ISO 32000-2, §7.11.3) — một trong tám giá trị tiêu chuẩn (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified), hoặc một giá trị tùy chỉnh — cho biết tệp liên quan như thế nào tới nội dung mà nó được đính kèm vào.
  • Mối quan hệ có gán kiểu đó là cơ chế đứng sau hóa đơn điện tử lai (ZUGFeRD / Factur-X) và tệp đính kèm PDF/A-3 (Spec: ISO 19005-3, PDF/A-3).
  • NextPDF hỗ trợ các nguyên thủy vật chứa thô trong core: embedFile()embedFileFromString() với một mối quan hệ rõ ràng. Các phiên bản Advanced bổ sung bộ nhúng hóa đơn điện tử EN 16931 / ZUGFeRD / Factur-X chuyên dụng đặt trên các nguyên thủy này.

Hãy nghĩ về nó như hai tầng xếp chồng lên nhau.

Tầng dưới là lưu trữ. Một embedded file stream (Spec: ISO 32000-2, §7.11.4) là các byte của tệp gốc được bọc trong một đối tượng PDF stream, với một parameters dictionary ghi lại kích thước gốc, ngày sửa đổi, và một checksum của dữ liệu chưa nén. Stream được tiếp cận qua một dictionary file specification/EF dictionary của nó trỏ tới embedded file stream — bản thân stream không mang /EF. Một trình đọc có thể lấy tệp ra trở lại từng-byte-một. Để các tệp này tìm được, document catalog giữ một EmbeddedFiles name tree — một ánh xạ đã sắp xếp từ một tên tới mỗi file specification — nhờ vậy một trình đọc có thể liệt kê “đây là 3 tệp bên trong tệp PDF này” mà không cần duyệt từng trang.

Tầng trên là ý nghĩa. Tự thân nó, một embedded file chỉ đơn thuần là có mặt. Cơ chế associated-files (Spec: ISO 32000-2, §14.13) gắn một tệp vào một thứ gì đó — cả tài liệu, một trang, một đối tượng đồ họa — và đóng dấu nó với một AFRelationship. ISO 32000-2 định nghĩa một bộ từ vựng nhỏ gồm tám giá trị tiêu chuẩn (Spec: ISO 32000-2, §7.11.3), và cũng cho phép các giá trị tùy chỉnh; mỗi giá trị tiêu chuẩn trả lời một câu hỏi chính xác:

AFRelationshipNó khẳng định điều gì về tệp
SourceĐây là tài liệu nguồn mà nội dung thấy được được tạo ra từ đó (ví dụ, tài liệu xử lý văn bản gốc).
DataĐây là dữ liệu có cấu trúc gắn với nội dung thấy được — trường hợp điển hình là XML hóa đơn đứng sau một trang hóa đơn đã được hiển thị.
AlternativeĐây là một biểu diễn thay thế của cùng nội dung (ví dụ, một phiên bản âm thanh hoặc video).
SupplementĐây là tài liệu bổ sung mở rộng nội dung nhưng không phải là một phần của nó.
EncryptedPayloadTệp được nhúng là một payload đã mã hóa mà tệp PDF bọc lại như một khối mờ đục.
FormDataTệp là dữ liệu form (FDF, XFDF, hoặc một payload form XML).
SchemaTệp là một schema mô tả cấu trúc của một tệp Data (ví dụ, một XSD cho dữ liệu XML hoặc một JSON Schema).
UnspecifiedMối quan hệ được chủ ý không nêu ra. Trung thực, nhưng nó chẳng nói cho máy biết điều gì.

Ngoài tám giá trị này, tiêu chuẩn cũng cho phép các giá trị quan hệ tùy chỉnh đặc thù ứng dụng, nên bộ từ vựng có thể mở rộng được chứ không cố định.

Một associated file được định nghĩa bởi hai thứ làm việc cùng nhau, không phải một khóa đơn lẻ. Liên kết /AF gắn file specification vào một phần của tài liệu; khóa AFRelationship trong file specification sau đó nêu rõ mối quan hệ ngữ nghĩa. Mục /AF trên điểm liên kết (document catalog, một trang, hoặc một đối tượng) là một mảng — mảng đó chứa một hoặc nhiều file specification dictionary, thường dưới dạng các indirect reference; /AF không phải là một reference đơn lẻ. Một associated file cấp tài liệu là file specification được liệt kê trong mảng /AF của document catalog, mang theo AFRelationship của nó. Đánh dấu bảng tính đó là Unspecified thì bạn đã liên kết nó với tài liệu nhưng chẳng nói cho máy biết gì về lý do. Đánh dấu cùng bảng tính đó là Data thì bạn đã nói cho mọi trình đọc tuân thủ biết nó là gì và nó dùng để làm gì. Các byte y hệt nhau. Ngữ nghĩa thì không.

Đây là lý do trường hợp hóa đơn điện tử không phải là “đính kèm một tệp XML”. Nó là “nhúng cái XML này làm associated file Data cho tài liệu này, bên trong một vật mang PDF/A-3 tuân thủ” — với tính hợp lệ của hóa đơn và sự chấp nhận pháp lý vẫn là những phép kiểm tra riêng mà vật mang không thực hiện. Luồng có bốn giai đoạn, và thứ tự là thứ giữ cho nó đúng.

  1. Store the bytesThe file is wrapped in an embedded file stream with its size, dates, and a checksum (ISO 32000-2 §7.11.4).
  2. Register it by nameThe file specification is added to the EmbeddedFiles name tree so a reader can enumerate attachments without scanning the document.
  3. Declare the relationshipAn AFRelationship value (one of the eight standard values such as Source or Data) marks how the file relates to the content, associated at the document level (ISO 32000-2 §14.13.3).
  4. Make it archivalA PDF/A-3 carrier permits the embedded payload to ride inside one conforming archival PDF/A document; invoice validity and legal acceptance remain separate checks (ISO 19005-3).
How a typed attachment becomes a hybrid file end to end: the engine stores the bytes, registers the file by name, declares the relationship, and the archival profile permits it all to ride inside one conforming archival document.

Giai đoạn thứ tư đó là lý do PDF/A-3 tồn tại như một profile riêng biệt. Các profile lưu trữ trước đó hạn chế cái gì có thể được nhúng; PDF/A-3 (Spec: ISO 19005-3, PDF/A-3) là phần cho phép các tệp ở bất kỳ định dạng nào đi kèm bên trong một tài liệu lưu trữ tuân thủ. Nó cho phép payload được nhúng — nó không xác nhận payload đó hay trao cho nó địa vị pháp lý. Nếu không có nó, hóa đơn lai — một tệp vừa là trang một người đọc vừa là dữ liệu một hệ thống thuế phân tích — sẽ không thể là một tài liệu lưu trữ PDF/A tuân thủ chút nào; còn việc hóa đơn có hợp lệ và được pháp luật chấp nhận hay không vẫn là một câu hỏi riêng. Bộ nhúng hóa đơn điện tử chuyên dụng mà các phiên bản Advanced bổ sung chính là lớp tiện ích đặt trên đúng cái này: nó nhúng payload, đặt mối quan hệ thành Data, và đăng ký nó cho đúng, nhờ vậy bạn không phải lắp ráp đường ống vật chứa bằng tay. Các cơ chế hóa đơn và lưu trữ sâu hơn nằm trên hai trang lân cận được liên kết bên dưới; trang này nói về cái vật chứa mà cả hai đều đứng trên đó.

Một chương trình nhỏ, hoàn chỉnh. Hai lời gọi quan trọng là khác biệt giữa một associated file không gán kiểu và một associated file có gán kiểu — và mối quan hệ là một đối số rõ ràng mà bạn nên đặt. Trong engine này, cả hai lời gọi đều tạo ra một associated file: embedFile()embedFileFromString() luôn đăng ký file specification vào mảng /AF của document catalog, nên thứ duy nhất mà mối quan hệ thay đổi là liên kết đó có nghĩa gì. Nó mặc định là Unspecified, tức là liên kết tệp nhưng chẳng nói cho máy biết lý do; với một payload hóa đơn điện tử bạn đặt nó thành Data để một trình đọc có thể tìm thấy nó.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
use NextPDF\Navigation\AFRelationship;
$document = Document::createStandalone();
$document->addPage();
$document->setFont('helvetica', 'B', 16);
$document->cell(0, 12, 'Invoice INV-2026-0042', newLine: true);
// An UNTYPED associated file: the bytes are embedded AND the file spec is
// added to the document catalog's /AF array, but the relationship says
// nothing about why. A reader can open it; a machine cannot tell its role.
// The relationship is left Unspecified (its default); the second argument is
// the human-readable description. embedFile accepts the AFRelationship enum.
$document->embedFile(
'/srv/invoices/INV-2026-0042-source.docx',
'Original source document',
AFRelationship::Unspecified,
);
// A TYPED associated file: the invoice XML is declared as the DATA behind
// the visible page. This is the relationship a hybrid e-invoice reader
// looks for — the same intent the dedicated e-invoice embedder sets.
// embedFileFromString takes the data, a filename, a description, and a
// relationship as a PDF-name string ('/Data').
$invoiceXml = $generateCiiXml(); // your ERP authors this; the engine never does
$document->embedFileFromString(
$invoiceXml,
'factur-x.xml',
'Factur-X invoice data',
'/Data',
);
$bytes = $document->getPdfData();

Mối quan hệ '/Data' là không thể nhầm lẫn. Tệp đính kèm đầu tiên — để là Unspecified — vẫn được liên kết y như vậy, chỉ là không có một ý nghĩa được nêu ra. Với cả hai lời gọi, engine ghi embedded file stream, thêm tệp vào EmbeddedFiles name tree, liệt kê file specification của nó trong mảng /AF của document catalog, và ghi lại mối quan hệ mà bạn đã nêu — nó không tự chọn một cái thay cho bạn. Engine này không có chế độ chỉ-name-tree: mọi tệp bạn nhúng theo cách này đều là một tài-liệu-associated file, nên mối quan hệ là đòn bẩy duy nhất bạn kiểm soát.

Giả định hay gặp là “embedded” và “associated” là hai từ cho cùng một thứ. Không phải. Embedded nói về lưu trữ — các byte nằm bên trong tệp PDF. Associated nói về liên kết — file specification được liệt kê trong một mảng /AF trên một phần của tài liệu, và nó mang theo một AFRelationship. Trong mô hình PDF trừu tượng một tệp có thể được nhúng vào name tree mà chẳng bao giờ được liên kết; nhưng đường đi embedFile() của NextPDF không để nó ở đó — nó luôn ghi liên kết /AF — nên đối với engine này câu hỏi còn bỏ ngỏ không bao giờ là liệu một tệp có được liên kết hay không mà là mối quan hệ nói điều gì.

Một cái bẫy thứ hai: giả định rằng một trình đọc sẽ “tự suy ra” tệp đính kèm nào là hóa đơn. Một trình đọc tuân thủ không được phép đoán. Nó tìm tệp mà mối quan hệ của nó nói Data. Để mối quan hệ là Unspecified thì bạn đã liên kết payload trong khi chẳng nói cho máy biết gì hữu ích về vai trò của nó.

Cơ chế vật chứa mạnh mẽ theo một cách đáng được nói thẳng: embedFile() đọc bất cứ đường dẫn nào mà tiến trình PHP có thể đọc. Đó là tính năng — và nó cũng là ranh giới. Engine đính kèm các byte mà nó được trao; nó không, và không thể, quyết định thay bạn xem liệu một đường dẫn có phải là thứ bạn chủ ý phơi ra hay không.

Embedding a file from a caller-supplied path — edition availability
EditionAvailability
Core

embedFile() reads any path the PHP process has access to and embeds its bytes verbatim. Validating that the path is safe and intended — not a user-controlled value, a traversal, or a secret outside the document’s scope — is the integrator’s responsibility. This is a documented security contract, not an oversight: the engine will not silently guess which paths are legitimate, because that guess belongs to your application, which knows the trust boundary the engine cannot see. Pass attacker-influenced bytes through a string with embedFileFromString() so the path layer is never in play.

ProNot in this edition
EnterpriseNot in this edition

Hai giới hạn nữa đáng nêu thẳng thừng:

  • Nhúng không phải là xác nhận. Engine mang các byte bạn trao cho nó. Việc cái XML được nhúng có phải là một payload hóa đơn tuân thủ hay không là một câu hỏi riêng, được trả lời bởi một validator — xem trang hóa đơn.
  • Một tệp đính kèm có gán kiểu tự thân nó không phải là một tệp lưu trữ tuân thủ. Làm cho tệp lai trở thành một tài liệu PDF/A-3 hợp pháp đòi hỏi chế độ lưu trữ và một phép kiểm tra tuân thủ độc lập — xem trang lưu trữ.
  • Invoices and e-invoicing — trường hợp dùng mà cơ chế này làm cho khả thi: một tệp PDF lai mang theo một hóa đơn đọc-được-bằng-máy làm associated file Data của nó.
  • Archival and PDF/A — vì sao vật mang là một tệp PDF/A-3 và sự tuân thủ hứa hẹn cũng như không hứa hẹn điều gì.
  • The anatomy of a PDF file — nơi name tree và document catalog nằm trong cấu trúc tệp.
  • Streams and filters — cách các byte của một embedded file được lưu và nén bên trong một đối tượng stream.
  • Embedded file stream — một đối tượng PDF stream giữ các byte của một tệp bên ngoài, với một parameters dictionary ghi lại kích thước gốc, các mốc ngày, và một checksum (ISO 32000-2 §7.11.4).
  • EmbeddedFiles name tree — ánh xạ đã sắp xếp trong document catalog liệt kê các embedded file theo tên, nhờ vậy một trình đọc có thể liệt kê các tệp đính kèm mà không cần quét cả tài liệu.
  • Associated file — một embedded file được gắn vào một phần của tài liệu bởi một liên kết /AF (trên document catalog, một trang, hoặc một đối tượng) và mang theo một AFRelationship nêu rõ nó liên quan thế nào tới nội dung đó; trường hợp cấp tài liệu — file specification trong mảng /AF của catalog — là cái trang này xoay quanh (ISO 32000-2 §14.13.3).
  • AFRelationship — khóa của file-specification mà giá trị của nó đặt tên cho mối quan hệ (ISO 32000-2 §7.11.3). Nó nhận một trong tám giá trị tiêu chuẩn (Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema, Unspecified) hoặc một giá trị tùy chỉnh; Data là giá trị mà một payload hóa đơn điện tử lai dùng.
  • PDF/A-3 — profile lưu trữ ISO 19005-3 cho phép các tệp ở bất kỳ định dạng nào được nhúng, làm cho một tài liệu lai tuân thủ trở nên khả thi.
  • Hóa đơn lai (Hybrid invoice) — một tệp PDF vừa là một trang đọc-được-bằng-người vừa là một payload hóa đơn được nhúng đọc-được-bằng-máy.