Articol tehnic

Fișiere atașate PDF în Delphi cu PDFium Component: Citire, Adăugare, Ștergere

Fișierele atașate într-un PDF sunt stocate în arborele de fișiere încorporate al documentului, o structură pe care majoritatea vizualizatoarelor o expun ca un panou cu agrafă de birou sau o bară laterală pentru atașamente. Din codul Delphi, PDFium Component expune acel arbore printr-un set restrâns de proprietăți indexate pe TPdf: parcurgeți elementele după indexul întreg, citiți numele și conținutul de octeți, creați intrări noi și le ștergeți pe cele existente. Suprafața API-ului este restrânsă; există doar câteva limitări legate de ordine și o regulă de igienizare ce merită cunoscute înainte de a scrie cod de producție în jurul acestei funcționalități

Citirea fișierelor atașate dintr-un document deschis

AttachmentCount indică numărul de fișiere încorporate declarate de document. Valoarea este citită direct din apelul PDFium subiacent, prin urmare reflectă exact ceea ce conține documentul PDF. De acolo, AttachmentName[Index] returnează numele de afișare ca un WString, iar Attachment[Index] oferă octeții bruți sub forma unui tablou TBytes. Ambii indici pornesc de la zero. Documentul trebuie să fie deschis (Pdf.Active = True) înainte de a interoga oricare dintre proprietăți; apelarea acestora pe un document închis returnează zero sau un rezultat gol, fără a genera excepții

Un aspect important: Attachment[Index] alocă și returnează întregul conținut al fișierului la fiecare citire. Pentru un document care conține o resursă încorporată mare, parcurgerea tuturor fișierelor atașate pentru a construi o listă de afișare implică un cost de alocare la fiecare apel. Dacă aveți nevoie de nume doar în scopul afișării, citiți mai întâi AttachmentName și amânați preluarea octeților până când utilizatorul solicită efectiv fișierul

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;

Extragerea unui atașament pe disc

Nu există o funcție ajutătoare de tip SaveAttachment. Citiți octeții și îi scrieți acolo unde aveți nevoie, ceea ce înseamnă că sarcina de construire și igienizare a căii îi revine exclusiv codului dvs. Acest lucru este important când numele atașamentelor provin din documente nesigure. Numele fișierelor atașate în PDF sunt șiruri stocate în interiorul fișierului; ele pot conține separatori de cale, caractere Unicode similare și alte elemente care vor produce rezultate neprevăzute dacă le trimiteți direct către TFileStream.Create. Treceți întotdeauna numele prin ExtractFileName înainte de a construi orice cale de ieșire și luați în considerare respingerea numelor care încep cu punct sau care conțin caractere ce diferă de cele așteptate de sistem

Tabloul de octeți returnat de Attachment[Index] este deținut de apelant. Scrieți-l pe disc folosind un TFileStream standard; îl puteți folosi cum doriți, inclusiv prin inspectarea primilor câțiva octeți pentru a verifica formatul real al fișierului în loc să aveți încredere în numele declarat

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;

Adăugarea fișierelor atașate și scrierea în doi pași

Crearea unui atașament necesită două apeluri, nu unul. CreateAttachment(Name) înregistrează o nouă poziție în arborele de fișiere încorporate și returnează True la succes. Acea poziție este inițial goală. Ulterior, alocați conținutul scriind în Attachment[AttachmentCount - 1], vizând cea mai recent creată intrare. Dacă funcția CreateAttachment returnează False, poziția nu a fost creată, iar alocarea ar putea corupe atașamentul de la indexul care se întâmplă să fie ultimul

După modificarea listei de atașamente, schimbările sunt prezente doar în memorie. Apelați SaveAs pentru a scrie un nou fișier cu arborele de fișiere încorporate actualizat. PDFium Component nu permite salvarea peste același fișier deschis în mod curent, deoarece motorul păstrează un handle de citire blocat pe sursă. Modelul standard pentru o actualizare locală (in-place) constă în salvarea pe o cale temporară, închiderea documentului, ștergerea sau redenumirea originalului, redenumirea fișierului temporar pe poziția corespunzătoare și redeschiderea acestuia

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;

Informații despre tipul atașamentului

Dincolo de nume și de conținutul de octeți, AttachmentType[Index] returnează șirul de tip MIME stocat în dicționarul de fișiere încorporate al PDF-ului, dacă a fost înregistrat când fișierul a fost atașat inițial. Multe generatoare lasă acest câmp gol sau îl setează la o valoare generică precum application/octet-stream, prin urmare nu vă puteți baza pe el pentru detectarea formatului într-un flux de producție. Pentru o identificare sigură, citiți primii câțiva octeți ai conținutului și căutați semnăturile de fișier cunoscute: %PDF pentru un PDF imbricat, antetul de fișier local ZIP PK\x03\x04 pentru documente Office Open XML, \xD0\xCF\x11\xE0 pentru fișiere binare compuse de tip vechi. Informațiile despre tip din dicționar sunt potrivite pentru afișarea într-o etichetă de interfață, dar nu ar trebui să dicteze deciziile de procesare când aveți octeții reali disponibili

Ștergerea atașamentelor

DeleteAttachment(Index) elimină intrarea din acea poziție și returnează True în caz de succes. După ștergere, intrările rămase se deplasează în jos, deci dacă ștergeți mai multe atașamente într-o buclă, trebuie să parcurgeți elementele de la ultimul index în jos, nu în sus, pentru a evita omiterea intrărilor după fiecare deplasare. Modificarea este păstrată în memorie până când apelați SaveAs

Un scenariu des întâlnit în fluxurile de procesare a documentelor constă în eliminarea tuturor atașamentelor dintr-un PDF primit înainte de a-l transmite mai departe, din motive de securitate sau dimensiune. Calculați numărul o singură dată înainte de buclă și parcurgeți în sens invers:

procedure StripAllAttachments(Pdf: TPdf);
var
  I: Integer;
begin
  for I := Pdf.AttachmentCount - 1 downto 0 do
    Pdf.DeleteAttachment(I);
end;

Unde apar fișierele atașate PDF în practică

API-ul pentru atașamente funcționează pe orice PDF pe care îl poate deschide PDFium, însă documentele în care întâlniți de regulă fișiere încorporate se rezumă la câteva cazuri specifice. Standardul PDF/A-3 (ISO 19005-3) permite în mod explicit fișiere încorporate conforme ca mecanism de grupare a datelor sursă alături de formatul de arhivare; facturile electronice ZUGFeRD și Factur-X se bazează exact pe acest mecanism pentru a include date XML structurate în interiorul aspectului PDF ușor de citit. PDF-urile derivate din e-mail-uri conțin uneori atașamentele originale redirecționate în arborele de fișiere încorporate. Documentația tehnică ce provine din sisteme de redactare structurate include uneori resurse de asistență în același mod

Când aplicația dvs. procesează fișiere PDF din afara organizației, verificarea AttachmentCount ca parte a recepției documentelor este utilă din două motive independente. În primul rând, fișierele încorporate pot conține date pe care doriți să le extrageți și să le procesați, cum ar fi fișierul XML din interiorul facturii PDF. În al doilea rând, fișierele încorporate pot conține conținut executabil arbitrar, așa că informația despre ceea ce este prezent contează chiar și atunci când nu intenționați să extrageți acele date. Niciunul dintre aceste motive nu necesită implementări complexe: citiți numărul de fișiere, verificați numele și decideți ce doriți să faceți cu octeții respectivi

Proprietățile pentru atașamente prezentate aici fac parte din produsul PDFium Component pentru Delphi și C++Builder