Tệp đính kèm PDF được lưu trữ trong cây tệp nhúng (embedded-file tree) của tài liệu, một cấu trúc mà hầu hết các trình xem hiển thị dưới dạng bảng có hình ghim giấy hoặc thanh bên tệp đính kèm. Từ mã Delphi, PDFium Component hiển thị cây đó thông qua một tập hợp nhỏ các thuộc tính được lập chỉ mục trên TPdf: bạn lặp lại bằng chỉ mục số nguyên, đọc tên và nội dung byte, tạo các vị trí mới và xóa các vị trí hiện có. Bề mặt API rất hẹp; chỉ có một vài ràng buộc về thứ tự và một quy tắc làm sạch đáng để biết trước khi bạn viết mã sản xuất xoay quanh nó
Đọc tệp đính kèm từ một tài liệu đang mở
AttachmentCount cho biết số lượng tệp nhúng mà tài liệu khai báo. Nó đọc trực tiếp từ lệnh gọi cơ bản của PDFium, do đó nó chỉ phản ánh những gì PDF thực sự chứa. Từ đó, AttachmentName[Index] trả về tên hiển thị dưới dạng WString, và Attachment[Index] cung cấp các byte thô dưới dạng mảng TBytes. Cả hai đều dựa trên chỉ số 0. Tài liệu phải được mở (Pdf.Active = True) trước khi bạn truy vấn bất kỳ thuộc tính nào; việc gọi chúng trên một tài liệu đã đóng sẽ cho bạn kết quả bằng 0 hoặc trống rỗng mà không có ngoại lệ
Một điều cần lưu ý: Attachment[Index] cấp phát và trả về toàn bộ nội dung tệp ở mỗi lần đọc. Đối với tài liệu mang tài sản nhúng lớn, việc lặp qua tất cả các tệp đính kèm để xây dựng danh sách hiển thị đồng nghĩa với việc phải trả chi phí cấp phát đó cho mỗi lệnh gọi. Nếu bạn chỉ cần tên cho mục đích hiển thị, hãy đọc AttachmentName trước và hoãn việc tìm nạp byte cho đến khi người dùng thực sự yêu cầu tệp
procedure ListAttachments(Pdf: TPdf);
var
I: Integer;
Data: TBytes;
begin
if not Pdf.Active then
Exit;
for I := 0 to Pdf.AttachmentCount - 1 do
begin
Data := Pdf.Attachment[I];
Writeln(Format('%d: %s (%d bytes)',
[I, Pdf.AttachmentName[I], Length(Data)]));
end;
end;
Trích xuất tệp đính kèm vào đĩa
Không có hàm trợ giúp SaveAttachment. Bạn đọc các byte và ghi chúng vào bất cứ đâu bạn cần, điều này đặt hoàn toàn việc xây dựng và làm sạch đường dẫn lên mã của bạn. Điều đó rất quan trọng khi tên tệp đính kèm đến từ các tài liệu không đáng tin cậy. Tên tệp đính kèm PDF là các chuỗi được lưu trữ bên trong tệp; chúng có thể chứa dấu phân cách đường dẫn, ký tự Unicode tương tự, và các ký tự khác sẽ tạo ra kết quả không mong muốn nếu bạn truyền chúng trực tiếp vào TFileStream.Create. Luôn chạy tên qua ExtractFileName trước khi xây dựng bất kỳ đường dẫn đầu ra nào và cân nhắc từ chối các tên bắt đầu bằng dấu chấm hoặc chứa các ký tự nằm ngoài những gì hệ thống của bạn mong đợi
Mảng byte được trả về bởi Attachment[Index] thuộc sở hữu của người gọi. Viết nó ra bằng một TFileStream bình thường và nó là của bạn để làm những gì bạn muốn, bao gồm cả việc kiểm tra vài byte đầu tiên để xác minh định dạng tệp thực tế thay vì tin tưởng vào tên đã khai báo
procedure ExtractAttachment(Pdf: TPdf; Index: Integer; const OutputDir: string);
var
SafeName: string;
OutPath: string;
Data: TBytes;
FS: TFileStream;
begin
SafeName := ExtractFileName(Pdf.AttachmentName[Index]);
if SafeName = '' then
SafeName := Format('attachment_%d', [Index]);
OutPath := IncludeTrailingPathDelimiter(OutputDir) + SafeName;
Data := Pdf.Attachment[Index];
FS := TFileStream.Create(OutPath, fmCreate);
try
if Length(Data) > 0 then
FS.WriteBuffer(Data[0], Length(Data));
finally
FS.Free;
end;
end;
Thêm tệp đính kèm và quá trình ghi hai bước
Việc tạo một tệp đính kèm cần hai lệnh gọi, không phải một. CreateAttachment(Name) đăng ký một vị trí mới trong cây tệp nhúng và trả về True khi thành công. Vị trí đó bắt đầu trống. Sau đó, bạn gán nội dung bằng cách viết vào Attachment[AttachmentCount - 1], nhắm mục tiêu vào mục nhập được tạo gần đây nhất. Nếu CreateAttachment trả về False, vị trí không được tạo và việc gán sẽ làm hỏng tệp đính kèm ở bất kỳ chỉ mục nào là chỉ mục cuối cùng
Sau khi sửa đổi danh sách tệp đính kèm, các thay đổi chỉ nằm trong bộ nhớ. Gọi SaveAs để ghi một tệp mới với cây tệp nhúng đã được cập nhật. PDFium Component không hỗ trợ lưu lại vào cùng một tệp hiện đang mở, vì công cụ giữ một xử lý đọc vào nguồn. Mẫu tiêu chuẩn cho bản cập nhật tại chỗ (in-place) là lưu vào một đường dẫn tạm thời, đóng tài liệu, xóa hoặc đổi tên tệp gốc, sau đó đổi tên tệp tạm thời vào vị trí và mở lại
procedure AddFileAttachment(Pdf: TPdf; const FilePath: string);
var
FS: TFileStream;
Data: TBytes;
AttachName: string;
begin
if not Pdf.Active then
Exit;
FS := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
try
SetLength(Data, FS.Size);
if FS.Size > 0 then
FS.ReadBuffer(Data[0], FS.Size);
finally
FS.Free;
end;
AttachName := ExtractFileName(FilePath);
if Pdf.CreateAttachment(AttachName) then
Pdf.Attachment[Pdf.AttachmentCount - 1] := Data;
end;
Thông tin loại tệp đính kèm
Bên cạnh tên và nội dung byte, AttachmentType[Index] trả về chuỗi loại MIME được lưu trữ trong từ điển tệp nhúng của PDF, nếu một chuỗi được ghi lại khi tệp được đính kèm ban đầu. Nhiều trình tạo (generator) để trống trường này hoặc đặt thành giá trị chung như application/octet-stream, do đó bạn không thể dựa vào trường này để phát hiện định dạng trong quy trình sản xuất. Để nhận dạng đáng tin cậy, hãy đọc vài byte đầu tiên của nội dung và kiểm tra các chữ ký tệp đã biết: %PDF cho tệp PDF lồng nhau, tiêu đề tệp cục bộ ZIP PK\x03\x04 cho tài liệu Office Open XML, \xD0\xCF\x11\xE0 cho các tệp nhị phân phức hợp (compound-file) cũ. Thông tin loại từ từ điển có thể hiển thị tốt trên nhãn giao diện người dùng, nhưng không nên điều khiển các quyết định xử lý khi bạn có sẵn các byte thực tế
Xóa tệp đính kèm
DeleteAttachment(Index) xóa mục nhập ở vị trí đó và trả về True khi thành công. Sau khi xóa, các mục nhập còn lại sẽ dịch chuyển xuống, do đó, nếu bạn đang xóa nhiều tệp đính kèm trong một vòng lặp, bạn phải lặp từ chỉ mục cuối cùng trở xuống, không phải trở lên, để tránh bỏ qua các mục nhập sau mỗi lần dịch chuyển. Thay đổi nằm trong bộ nhớ cho đến khi bạn gọi SaveAs
Một kịch bản phổ biến trong các quy trình xử lý tài liệu là loại bỏ tất cả các tệp đính kèm khỏi PDF đến trước khi chuyển nó xuống hạ nguồn, vì lý do bảo mật hoặc kích thước. Đếm một lần trước vòng lặp và lặp ngược lại:
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Tệp đính kèm PDF xuất hiện ở đâu trong thực tế
API đính kèm hoạt động trên bất kỳ PDF nào mà PDFium có thể mở, nhưng các tài liệu mà bạn thực sự gặp tệp nhúng xoay quanh một vài trường hợp cụ thể. PDF/A-3 (ISO 19005-3) cho phép rõ ràng các tệp nhúng phù hợp làm cơ chế để gói dữ liệu nguồn cùng với bản trình diễn lưu trữ; Hóa đơn điện tử ZUGFeRD và Factur-X hoàn toàn dựa vào điều này để nhúng tải trọng XML có cấu trúc bên trong bố cục PDF mà con người có thể đọc được. Các tệp PDF có nguồn gốc từ email đôi khi mang các tệp đính kèm tin nhắn gốc của chúng được chuyển tiếp vào cây tệp nhúng. Tài liệu kỹ thuật bắt nguồn từ các hệ thống soạn thảo có cấu trúc đôi khi nhóm các tài sản hỗ trợ theo cùng một cách
Khi ứng dụng của bạn xử lý các tệp PDF gửi đến từ bên ngoài tổ chức của bạn, việc kiểm tra AttachmentCount như một phần của quá trình tiếp nhận tài liệu là đáng làm vì hai lý do độc lập. Đầu tiên, tệp nhúng có thể chứa dữ liệu bạn muốn trích xuất và xử lý, chẳng hạn như XML bên trong PDF hóa đơn. Thứ hai, các tệp nhúng có thể mang nội dung thực thi tùy ý, vì vậy việc biết những gì hiện có là rất quan trọng ngay cả khi bạn không bao giờ có ý định trích xuất nó. Cả hai lý do đều không yêu cầu bạn làm bất cứ điều gì phức tạp: đọc số lượng, kiểm tra tên và quyết định phải làm gì với các byte
Các thuộc tính tệp đính kèm được hiển thị ở đây là một phần của PDFium Component dành cho Delphi và C++Builder