Technical Article

Priloge PDF v Delphiju s PDFium VCL: Branje, dodajanje, brisanje

Priloge datotek PDF so shranjene v drevesu vgrajenih datotek dokumenta, kar večina pregledovalnikov prikaže kot ploščo s sponko ali stransko vrstico s prilogami. V kodi Delphi komponenta PDFium VCL to drevo izpostavi prek majhnega nabora indeksiranih lastnosti na TPdf: iterirate po celoštevilskem indeksu, berete imena in vsebino bajtov, ustvarjate nova mesta in brišete obstoječa. API je ozek; obstaja le nekaj omejitev glede vrstnega reda in eno pravilo o sanitaciji, ki ga je dobro poznati pred pisanjem produkcijske kode.

Branje prilog iz odprtega dokumenta

AttachmentCount poda število vgrajenih datotek, ki jih dokument deklarira. To vrednost prebere neposredno iz klica PDFium, zato odraža le tisto, kar PDF dejansko vsebuje. Od tam AttachmentName[Index] vrne prikazano ime kot WString, Attachment[Index] pa vrne surove bajte kot polje TBytes. Obe lastnosti uporabljata indeks z začetkom pri nič. Dokument mora biti odprt (Pdf.Active = True), preden poizvedujete po kateri koli od teh lastnosti; klic na zaprtem dokumentu vrne nič ali prazen rezultat brez sprožitve izjeme.

Ena stvar, ki jo morate upoštevati: Attachment[Index] pri vsakem branju dodeli in vrne celotno vsebino datoteke. Za dokument z velikim vgrajenim sredstvom iteracija skozi vse priloge za gradnjo seznama prikaza pomeni plačilo stroškov dodelitve pri vsakem klicu. Če potrebujete imena le za namene prikaza, najprej preberite AttachmentName in odložite pridobivanje bajtov, dokler uporabnik dejansko ne zahteva datoteke.

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;

Izvoženje priloge na disk

Pomožne funkcije SaveAttachment ni. Bajte preberete in jih zapišete kamor koli potrebujete, kar pomeni, da sta gradnja in sanitacija poti v celoti prepuščeni vaši kodi. To je pomembno, kadar imena prilog prihajajo iz nepreverjenih dokumentov. Imena prilog PDF so nizi, shranjeni v datoteki; lahko vsebujejo ločila poti, podobne znake Unicode in druge znake, ki bodo povzročili nepričakovane rezultate, če jih posredujete neposredno v TFileStream.Create. Pred gradnjo katere koli izhodne poti vedno preusmerite ime skozi ExtractFileName in razmislite o zavrnitvi imen, ki se začnejo s piko ali vsebujejo znake zunaj sistemskih pričakovanj.

Polje bajtov, ki ga vrne Attachment[Index], je v lasti klicatelja. Zapišite ga z navadnim TFileStream in z njim lahko počnete kar želite, vključno s pregledom prvih nekaj bajtov za preverjanje dejanskega formata datoteke, namesto zanašanja na deklarirano ime.

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;

Dodajanje prilog in dvostopenjsko zapisovanje

Ustvarjanje priloge zahteva dva klica in ne enega. CreateAttachment(Name) registrira novo mesto v drevesu vgrajenih datotek in vrne True ob uspehu. To mesto se začne prazno. Nato dodelite vsebino z zapisovanjem v Attachment[AttachmentCount - 1], s čimer ciljate na zadnji ustvarjeni vnos. Če CreateAttachment vrne False, mesto ni bilo ustvarjeno, dodelitev pa bi poškodovala prilogo na indeksu, ki je trenutno zadnji.

Po spreminjanju seznama prilog spremembe živijo le v pomnilniku. Pokličite SaveAs za zapis nove datoteke s posodobljenim drevesom vgrajenih datotek. PDFium VCL trenutno ne podpira shranjevanja nazaj v isto odprto datoteko, saj mehanizem drži ročaj za branje vira. Standardni vzorec za posodobitev na mestu je shranjevanje na začasno pot, zapiranje dokumenta, brisanje ali preimenovanje izvirnika ter nato preimenovanje začasne datoteke na pravo mesto in ponovno odpiranje.

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;

Podatki o vrsti priloge

Poleg imena in vsebine bajtov AttachmentType[Index] vrne niz vrste MIME, shranjen v slovarju vgrajene datoteke PDF-ja, če je bil ta zapisan ob prvotni prilogi. Veliko generatorjev pusti to polje prazno ali pa ga nastavi na splošno vrednost, kot je application/octet-stream, zato se nanj ne morete zanašati za zaznavanje formata v produkcijskem cevovodu. Za zanesljivo identifikacijo preberite prvih nekaj bajtov vsebine in preverite znane podpise datotek: %PDF za gnezdeni PDF, glavo lokalne datoteke ZIP PK za dokumente Office Open XML ali ÐÏà za starejše binarne datoteke. Podatki o vrsti iz slovarja so primerni za prikaz v uporabniškem vmesniku, vendar ne bi smeli voditi odločitev o obdelavi, ko so na voljo dejanski bajti.

Brisanje prilog

DeleteAttachment(Index) odstrani vnos na tem položaju in vrne True ob uspehu. Po brisanju se preostali vnosi pomaknejo navzdol, zato morate pri brisanju več prilog v zanki iterirati od zadnjega indeksa navzdol in ne navzgor, da se izognete preskakovanju vnosov po vsakem pomiku. Sprememba je v pomnilniku, dokler ne pokličete SaveAs.

Splošen scenarij v cevovodih za obdelavo dokumentov je odstranitev vseh prilog iz prejetega PDF-ja iz varnostnih razlogov ali zaradi velikosti, preden se pošlje naprej. Preštejte enkrat pred zanko in iterirajte v obratnem vrstnem redu:

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

Kje se priloge PDF pojavljajo v praksi

API za priloge deluje na katerem koli PDF-ju, ki ga PDFium lahko odpre, vendar se dokumenti, v katerih dejansko naletite na vgrajene datoteke, zbirajo okoli nekaj specifičnih primerov. PDF/A-3 (ISO 19005-3) eksplicitno dovoljuje skladne vgrajene datoteke kot mehanizem za povezovanje izvornih podatkov poleg arhivske različice; elektronski računi ZUGFeRD in Factur-X se zanašajo natanko na to, da vgradijo strukturirano XML vsebino znotraj človeku berljive postavitve PDF. PDF-ji, izpeljani iz e-pošte, včasih prenašajo svoje izvirne priloge sporočil, posredovane v drevo vgrajenih datotek. Tehnična dokumentacija, ki izvira iz strukturiranih avtorskih sistemov, občasno na enak način združuje podporna sredstva.

Ko vaša aplikacija obdeluje vhodne PDF-je izven vaše organizacije, je preverjanje AttachmentCount kot del sprejema dokumenta koristno opraviti iz dveh neodvisnih razlogov. Prvič, vgrajene datoteke lahko vsebujejo podatke, ki jih želite izbrati in obdelati, na primer XML znotraj PDF računa. Drugič, vgrajene datoteke lahko vsebujejo poljubno izvedljivo vsebino, zato je poznavanje prisotnosti pomembno, tudi če je ne nameravate nikoli izbrati. Noben razlog ne zahteva zapletenih dejanj: preberite število, preverite imena in se odločite, kaj storiti z bajti.

Lastnosti prilog, prikazane tukaj, so del komponente PDFium VCL za Delphi in C++Builder.