Tehnički članak

PDF Attachments in Delphi with PDFium Component: Read, Add, Delete

Privitci PDF datoteka pohranjeni su u stablu ugrađenih datoteka dokumenta (embedded-file tree), strukturi koju većina preglednika prikazuje kao ploču sa spajalicom ili bočnu traku s privitcima. Iz Delphi koda, PDFium komponenta izlaže to stablo kroz mali skup indeksiranih svojstava na TPdf: prolazite kroz cijele brojeve indeksa, čitate nazive i bajtove sadržaja, stvarate nova mjesta (slots) i brišete postojeća. API sučelje je usko; postoji samo nekoliko ograničenja u pogledu redoslijeda i jedno pravilo o sanaciji koje vrijedi znati prije pisanja produkcijskog koda oko toga

Čitanje privitaka iz otvorenog dokumenta

AttachmentCount daje broj ugrađenih datoteka koje dokument deklarira. On čita izravno iz osnovnog poziva PDFium-a, tako da odražava samo ono što PDF stvarno sadrži. Odatle, AttachmentName[Index] vraća naziv za prikaz kao WString, a Attachment[Index] isporučuje sirove bajtove kao niz TBytes. Oba su indeksa bazirana na nuli. Dokument mora biti otvoren (Pdf.Active = True) prije nego što zatražite bilo koje od tih svojstava; pozivanje istih na zatvorenom dokumentu daje nulu ili prazan rezultat bez iznimke

Jedna stvar koju treba imati na umu: Attachment[Index] dodjeljuje i vraća puni sadržaj datoteke pri svakom čitanju. Za dokument koji nosi veliku ugrađenu datoteku, prolazak kroz sve privitke radi izgradnje popisa za prikaz znači plaćanje tog troška alokacije pri svakom pozivu. Ako su vam nazivi potrebni samo za potrebe prikaza, prvo pročitajte AttachmentName i odgodite dohvaćanje bajtova dok korisnik stvarno ne zatraži datoteku

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;

Izdvajanje privitka na disk

Ne postoji pomoćna funkcija SaveAttachment helper. Vi čitate bajtove i zapisujete ih gdje god vam je potrebno, što konstrukciju i sanaciju (sanitization) putanje u potpunosti prepušta vašem kodu. To je važno kada nazivi privitaka dolaze iz nepouzdanih dokumenata. Nazivi PDF privitaka su tekstualni nizovi pohranjeni unutar datoteke; mogu sadržavati separatore putanja, Unicode vizualne dvojnike i druge znakove koji će proizvesti neočekivane rezultate ako ih proslijedite izravno u TFileStream.Create. Uvijek provucite naziv kroz ExtractFileName prije izgradnje bilo koje izlazne putanje i razmislite o odbijanju naziva koji počinju s točkom ili sadrže znakove izvan onoga što vaš sustav očekuje

Niz bajtova koji vraća Attachment[Index] pripada pozivatelju. Zapišite ga pomoću uobičajenog TFileStream-a i s njim možete raditi što želite, uključujući i pregled prvih nekoliko bajtova kako biste provjerili stvarni format datoteke radije nego da vjerujete deklariranom nazivu

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;

Dodavanje privitaka i pisanje u dva koraka

Stvaranje privitka zahtijeva dva poziva, a ne jedan. Funkcija CreateAttachment(Name) registrira novo mjesto (slot) u stablu ugrađenih datoteka i vraća True u slučaju uspjeha. To mjesto započinje prazno. Zatim dodjelujete sadržaj pisanjem u Attachment[AttachmentCount - 1], ciljajući najnovije stvoreni unos. Ako CreateAttachment vrati False, mjesto nije stvoreno i dodjela bi oštetila privitak na indeksu koji se slučajno nađe kao posljednji

Nakon izmjene popisa privitaka, promjene žive samo u memoriji. Pozovite SaveAs za zapisivanje nove datoteke s ažuriranim stablom ugrađenih datoteka. PDFium komponenta trenutno ne podržava spremanje natrag u istu datoteku koja je otvorena, jer mehanizam drži ručku (handle) za čitanje na izvoru. Standardni obrazac za lokalno ažuriranje (in-place update) je spremanje na privremenu putanju, zatvaranje dokumenta, brisanje ili preimenovanje izvornika, a zatim preimenovanje privremene datoteke na njezino mjesto i ponovno otvaranje

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;

Informacije o vrsti privitka

Osim naziva i sadržaja bajtova, AttachmentType[Index] vraća MIME vrstu (MIME type string) pohranjenu u rječniku ugrađenih datoteka PDF-a, ako je bila zabilježena kada je datoteka prvotno priložena. Mnogi generatori ostavljaju ovo polje praznim ili ga postavljaju na generičku vrijednost poput application/octet-stream, pa se na njega ne možete osloniti za otkrivanje formata u produkcijskom cjevovodu. Za pouzdanu identifikaciju, pročitajte prvih nekoliko bajtova sadržaja i provjerite poznate potpise datoteka: %PDF za ugniježđeni PDF, ZIP zaglavlje lokalne datoteke PK\x03\x04 za dokumente Office Open XML, \xD0\xCF\x11\xE0 za naslijeđene binarne datoteke složenih datoteka (compound-file). Informacije o vrsti iz rječnika su u redu za prikaz u oznaci korisničkog sučelja, ali ne bi trebale upravljati odlukama o obradi kada su vam dostupni stvarni bajtovi

Brisanje privitaka

Funkcija DeleteAttachment(Index) uklanja unos na tom položaju i vraća True u slučaju uspjeha. Nakon brisanja, preostali unosi se pomiču prema dolje, pa ako brišete više privitaka u petlji, morate prolaziti od posljednjeg indeksa prema dolje, a ne prema naprijed, kako biste izbjegli preskakanje unosa nakon svakog pomaka. Promjena je u memoriji sve dok ne pozovete SaveAs

Uobičajeni scenarij u cjevovodima za obradu dokumenata je uklanjanje svih privitaka iz dolaznog PDF-a prije nego što se proslijedi dalje, iz sigurnosnih razloga ili razloga veličine. Prebrojite jednom prije petlje i prolazite u obrnutom redoslijedu:

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

Gdje se PDF privitci pojavljuju u praksi

API za privitke radi na svakom PDF-u koji PDFium može otvoriti, no dokumenti u kojima se stvarno susrećete s ugrađenim datotekama grupiraju se oko nekoliko specifičnih slučajeva. PDF/A-3 (ISO 19005-3) izričito dopušta sukladne ugrađene datoteke kao mehanizam za pakiranje izvornih podataka uz arhivski prikaz; elektronički računi ZUGFeRD i Factur-X oslanjaju se upravo na to kako bi ugradili strukturirani XML sadržaj unutar ljudima čitljivog PDF izgleda. PDF-ovi dobiveni iz e-pošte ponekad nose svoje izvorne privitke poruka proslijeđene u stablo ugrađenih datoteka. Tehnička dokumentacija koja potječe iz strukturiranih sustava za autorizaciju povremeno na isti način pakira popratna sredstva (assets)

Kada vaša aplikacija obrađuje dolazne PDF-ove izvan vaše organizacije, provjeru AttachmentCount isplati se provesti u sklopu unosa dokumenata iz dva neovisna razloga. Prvo, ugrađene datoteke mogu nositi podatke koje želite izdvojiti i obraditi, poput XML-a unutar PDF računa. Drugo, ugrađene datoteke mogu nositi proizvoljan izvršni sadržaj, pa je važno znati što je prisutno čak i kada to nikada ne namjeravate izdvojiti. Nijedan od tih razloga ne zahtijeva da učinite išta komplicirano: pročitajte broj, provjerite nazive i odlučite što učiniti s bajtovima

Svojstva privitaka prikazana ovdje dio su PDFium komponente za Delphi i C++Builder