Załączniki plików PDF są przechowywane w strukturze drzewa osadzonych plików dokumentu, którą większość przeglądarek prezentuje w formie panelu ze spinaczem lub paska bocznego załączników. W kodzie Delphi komponent PDFium Component udostępnia to drzewo za pomocą zestawu indeksowanych właściwości obiektu TPdf: można wykonywać iteracje po indeksach liczbowych, odczytywać nazwy i zawartość bajtową, tworzyć nowe pozycje oraz usuwać istniejące. Interfejs API jest zwięzły; istnieje tylko kilka ograniczeń dotyczących kolejności oraz jedna zasada oczyszczania ścieżek, którą warto znać przed napisaniem kodu produkcyjnego
Odczytywanie załączników z otwartego dokumentu
Właściwość AttachmentCount zwraca liczbę osadzonych plików zadeklarowanych w dokumencie. Pobiera dane bezpośrednio z biblioteki PDFium, odzwierciedlając faktyczną zawartość pliku PDF. Z kolei właściwość AttachmentName[Index] zwraca nazwę wyświetlaną jako WString, a Attachment[Index] dostarcza surowe dane w postaci tablicy TBytes. Indeksowanie obu właściwości zaczyna się od zera. Przed odpytaniem o te właściwości dokument musi być otwarty (Pdf.Active = True); wywołanie ich na zamkniętym dokumencie zwróci wartość zero lub pusty wynik bez zgłaszania wyjątków
Warto pamiętać o jednej rzeczy: wywołanie Attachment[Index] przy każdym odczycie alokuje pamięć i pobiera całą zawartość pliku. W przypadku dokumentu z dużym osadzonym plikiem iteracja po wszystkich załącznikach w celu zbudowania listy wyświetlania wiąże się z wysokim kosztem alokacji pamięci przy każdym przejściu. Jeśli potrzebujesz wyłącznie nazw do wyświetlenia na liście, odczytaj najpierw właściwość AttachmentName, a pobieranie bajtów odłóż do momentu, gdy użytkownik faktycznie zażąda otwarcia pliku
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;
Zapisywanie załącznika na dysku
Komponent nie oferuje dedykowanej metody pomocniczej typu SaveAttachment. Odczytujesz bajty i zapisujesz je samodzielnie, co oznacza, że tworzenie i weryfikacja ścieżki zapisu leży w całości po stronie Twojego kodu. Ma to duże znaczenie, gdy nazwy załączników pochodzą z niezaufanych dokumentów. Nazwy te są ciągami znaków zapisanymi wewnątrz pliku i mogą zawierać separatory katalogów, znaki zastępcze Unicode oraz inne elementy mogące wywołać nieoczekiwane zachowanie systemu po bezpośrednim przekazaniu ich do TFileStream.Create. Zawsze przepuść nazwę przez funkcję ExtractFileName przed utworzeniem ścieżki wyjściowej i rozważ odrzucanie nazw zaczynających się od kropki lub zawierających znaki niezgodne z systemem operacyjnym
Tablica bajtów zwracana przez Attachment[Index] przechodzi na własność wywołującego. Zapisz ją za pomocą standardowego strumienia TFileStream, co daje pełną kontrolę nad danymi, umożliwiając m.in. weryfikację pierwszych bajtów w celu potwierdzenia rzeczywistego formatu pliku zamiast bezkrytycznego ufania zadeklarowanej nazwie
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;
Dodawanie załączników i dwuetapowy zapis
Utworzenie załącznika wymaga wykonania dwóch kroków. Wywołanie CreateAttachment(Name) rejestruje nowy element w drzewe osadzonych plików i zwraca wartość True w przypadku powodzenia. Utworzony element jest początkowo pusty. Następnie przypisujesz dane, zapisując je pod indeksem Attachment[AttachmentCount - 1], wskazującym na ostatnio utworzoną pozycję. Jeśli funkcja CreateAttachment zwróci False, element nie został utworzony, a próba przypisania mogła uszkodzić dane pod ostatnim istniejącym indeksu
Po modyfikacji listy załączników zmiany są przechowywane wyłącznie w pamięci. Wywołaj metodę SaveAs, aby zapisać nowy plik ze zaktualizowanym drzewem osadzonych plików. Komponent PDFium Component nie obsługuje bezpośredniego nadpisywania aktualnie otwartego pliku, ponieważ silnik posiada aktywny uchwyt odczytu do pliku źródłowego. Standardowa procedura aktualizacji w miejscu polega na zapisie do ścieżki tymczasowej, zamknięciu dokumentu, usunięciu lub zmianie nazwy oryginału, a następnie przeniesieniu pliku tymczasowego na właściwe miejsce i ponownym jego otwarciu
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;
Informacje o typie załącznika
Oprócz nazwy i zawartości bajtowej, właściwość AttachmentType[Index] zwraca ciąg typu MIME zapisany w słowniku osadzonego pliku PDF, o ile został tam wprowadzony przy dodawaniu załącznika. Wiele programów generujących pliki PDF pozostawia to pole puste lub ustawia ogólną wartość, np. application/octet-stream, stąd nie można na nim polegać przy automatycznym przetwarzaniu. W celu wiarygodnej identyfikacji odczytaj pierwsze bajty zawartości i sprawdź charakterystyczne sygnatury plików: %PDF dla zagnieżdżonych plików PDF, nagłówek ZIP PK\x03\x04 dla dokumentów pakietu Office Open XML lub \xD0\xCF\x11\xE0 dla starszych binarnych plików złożonych (OLE). Informacja o typie ze słownika nadaje się do wyświetlenia w interfejsie użytkownika, lecz nie powinna sterować logiką biznesową aplikacji
Usuwanie załączników
Funkcja DeleteAttachment(Index) usuwa załącznik na podanej pozycji i zwraca wartość True w przypadku powodzenia. Po usunięciu elementy o wyższych indeksach są przesuwane w dół, stąd przy usuwaniu wielu załączników w pętli należy iterować od najwyższego indeksu w dół, a nie w górę, aby uniknąć pominięcia elementów po przesunięciu. Zmiany są zatwierdzane w pliku dopiero po wywołaniu SaveAs
Typowym scenariuszem w procesach przetwarzania dokumentów jest usuwanie wszystkich załączników z przychodzących plików PDF ze względów bezpieczeństwa lub w celu zmniejszenia rozmiaru pliku. Sprawdź liczbę załączników przed pętlą i wykonaj iterację wsteczną:
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Zastosowanie załączników PDF w praktyce
API załączników działa z każdym plikiem PDF, który biblioteka PDFium potrafi otworzyć, lecz w praktyce dokumenty zawierające osadzone pliki ograniczają się do kilku konkretnych przypadków. Standard PDF/A-3 (ISO 19005-3) wprost zezwala na dołączanie plików w celu powiązania danych źródłowych z wersją archiwalną; faktury elektroniczne w formatach ZUGFeRD i Factur-X wykorzystują ten mechanizm do dołączania strukturalnego pliku XML do czytelnej dla człowieka faktury PDF. Pliki PDF pochodzące z wiadomości e-mail czasami zawierają oryginalne załączniki poczty przeniesione do drzewa osadzonych plików. Z kolei dokumentacja techniczna tworzona w zaawansowanych systemach redakcyjnych okazjonalnie pakuje w ten sposób powiązane zasoby
Podczas przetwarzania zewnętrznych plików PDF w organizacji, weryfikacja właściwości AttachmentCount na etapie odbioru dokumentu jest zalecana z dwóch powodów. Po pierwsze, osadzone pliki mogą zawierać cenne dane biznesowe do przetworzenia, np. plik XML wewnątrz faktury PDF. Po drugie, załączniki mogą zawierać niebezpieczną zawartość wykonywalną, stąd warto kontrolować ich obecność nawet wtedy, gdy nie planujemy ich wyodrębniania. Żadna z tych operacji nie wymaga skomplikowanego kodu: odczytaj liczbę załączników, sprawdź ich nazwy i podejmij decyzję o dalszym postępowaniu z danymi
Opisane tu właściwości załączników wchodzą w skład komponentu PDFium Component dla Delphi i C++Buildera