Odborný článok

Prílohy PDF v Delphi pomocou PDFium Component: čítanie, pridávanie, mazanie

Prílohy súborov PDF sa ukladajú do stromu vložených súborov dokumentu, čo je štruktúra, ktorú väčšina prehliadačov zobrazuje ako panel so sponkou alebo ako bočný panel príloh. Z kódu v Delphi sprístupňuje PDFium Component tento strom prostredníctvom malej sady indexovaných vlastností triedy TPdf: prechádzate ich pomocou celočíselného indexu, čítate názvy a polia bajtov, vytvárate nové položky a mažete tie existujúce. Rozhranie API je úzke; pred písaním produkčného kódu stojí za to spoznať niekoľko obmedzení poradia a jedno pravidlo na ošetrenie ciest

Čítanie príloh z otvoreného dokumentu

Vlastnosť AttachmentCount udáva počet vložených súborov, ktoré dokument deklaruje. Číta sa priamo z podkladového volania PDFium, takže odráža iba to, čo PDF skutočne obsahuje. Vlastnosť AttachmentName[Index] potom vracia zobrazovaný názov ako WString a indexovaná vlastnosť Attachment[Index] poskytuje čisté bajty vo forme poľa TBytes. Obe vlastnosti sú indexované od nuly. Pred dopytovaním na ktorúkoľvek z nich musí byť dokument otvorený (Pdf.Active = True); volanie nad zatvoreným dokumentom vráti nulu alebo prázdny výsledok bez vyvolania výnimky

Na jednu vec netreba zabúdať: vlastnosť Attachment[Index] pri každom čítaní alokuje a vracia kompletný obsah súboru. Pri dokumente, ktorý nesie veľký vložený objekt, znamená prechádzanie všetkých príloh na vytvorenie zoznamu zaplatenie nákladov na alokáciu pri každom volaní. Ak potrebujete iba názvy na zobrazenie v zozname, prečítajte najprv AttachmentName a načítanie bajtov odložte až na moment, keď používateľ súbor skutočne vyžiada

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;

Uloženie prílohy na disk

K dispozícii nie je žiadna pomocná metóda SaveAttachment. Bajty prečítate a zapíšete ich kamkoľvek potrebujete, čo znamená, že zostavenie cesty a jej ošetrenie (sanitization) leží plne na vašom kóde. Je to dôležité najmä vtedy, keď názvy príloh pochádzajú z nedôveryhodných dokumentov. Názvy príloh PDF sú reťazce uložené vo vnútri súboru; môžu obsahovať oddeľovače ciest, podobné Unicode znaky a iné symboly, ktoré prinesú neočakávané výsledky, ak ich priamo odovzdáte do TFileStream.Create. Pred zostavením akejkoľvek výstupnej cesty vždy preveďte názov cez funkciu ExtractFileName a zvážte odmietnutie názvov, ktoré začínajú bodkou alebo obsahujú znaky, ktoré váš systém neočakáva

Pole bajtov vrátené vlastnosťou Attachment[Index] vlastní volajúci kód. Zapíšte ho pomocou bežného TFileStream a môžete s ním nakladať podľa uváženia, vrátane kontroly prvých niekoľkých bajtov na overenie skutočného formátu súboru namiesto slepej dôvery voči deklarovanému názvu

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;

Pridávanie príloh a dvojstupňový zápis

Vytvorenie prílohy si vyžaduje dve volania a nie jedno. CreateAttachment(Name) zaregistruje novú položku v strome vložených súborov a pri úspechu vráti True. Táto položka je na začiatku prázdna. Obsah jej potom priradíte zápisom do vlastnosti Attachment[AttachmentCount - 1], čím zacielite na posledný vytvorený záznam. Ak CreateAttachment vráti False, položka sa nevytvorila a zápis by poškodil prílohu na indexe, ktorý je zhodou okolností aktuálne posledný

Po úprave zoznamu príloh existujú zmeny iba v pamäti. Zavolajte SaveAs na zápis nového súboru s aktualizovaným stromom vložených súborov. PDFium Component momentálne nepodporuje ukladanie priamo do rovnakého, aktuálne otvoreného súboru, pretože engine si udržiava otvorený popisovač (handle) na čítanie zdroja. Štandardným postupom pre aktualizáciu na mieste je uloženie do dočasnej cesty, zatvorenie dokumentu, vymazanie alebo premenovanie originálu, premenovanie dočasného súboru na pôvodné miesto a jeho opätovné otvorenie

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;

Informácie o type prílohy

Okrem názvu a obsahu bajtov vracia vlastnosť AttachmentType[Index] reťazec typu MIME uložený v slovníku vloženého súboru PDF, ak bol pri pôvodnom pripojení zaznamenaný. Mnohé generátory nechávajú toto pole prázdne alebo ho nastavujú na univerzálnu hodnotu ako application/octet-stream, takže sa naň v produkčnej linke nemôžete spoliehať pri určovaní formátu. Pre spoľahlivú identifikáciu prečítajte prvých niekoľko bajtov obsahu a overte známe signatúry súborov: %PDF pre vnorené PDF, hlavičku lokálneho ZIP súboru PK\x03\x04 pre dokumenty Office Open XML, \xD0\xCF\x11\xE0 pre staršie binárne formáty compound file. Informácia o type zo slovníka je vhodná na zobrazenie v rozhraní, no nemala by riadiť spracovanie, keď máte k dispozícii skutočné bajty

Mazanie príloh

Metóda DeleteAttachment(Index) odstráni položku na danej pozícii a pri úspechu vráti True. Po vymazaní sa zostávajúce položky posunú nadol, takže ak vymazávate viacero príloh v cykle, musíte postupovať od posledného indexu smerom nadol a nie nahor, aby ste sa vyhli preskakovaniu položiek po každom posune. Zmena sa prejaví iba v pamäti, kým nezavoláte SaveAs

Častým scenárom v systémoch na spracovanie dokumentov je odstránenie všetkých príloh z prichádzajúceho PDF pred jeho ďalším odoslaním, a to z bezpečnostných dôvodov alebo kvôli zníženiu veľkosti. Zistite počet pred cyklom a prechádzajte ním odzadu:

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

Kde sa prílohy PDF vyskytujú v praxi

Norma PDF/A-3 (ISO 19005-3) výslovne povoľuje vyhovujúce vložené súbory ako mechanizmus na pribalenie zdrojových dát k archívnej verzii; elektronické faktúry ZUGFeRD a Factur-X sa spoliehajú presne na toto, aby vložili štruktúrovaný XML obsah priamo do ľudsky čitateľného vzhľadu PDF. Dokumenty PDF vytvorené z e-mailov niekedy nesú svoje pôvodné prílohy správ prenesené do tohto stromu vložených súborov. Technická dokumentácia pochádzajúca zo štruktúrovaných publikačných systémov občas podobným spôsobom pribaluje sprievodné podklady

Keď vaša aplikácia spracováva prichádzajúce PDF zvonku, kontrola vlastnosti AttachmentCount v rámci príjmu dokumentu stojí za zváženie z dvoch nezávislých dôvodov. Po prvé, vložené súbory môžu niesť dáta, ktoré chcete extrahovať a spracovať, ako napríklad XML vo vnútri faktúry PDF. Po druhé, vložené súbory môžu niesť ľubovoľný spustiteľný obsah, takže informácia o ich prítomnosti je dôležitá aj vtedy, keď ich nikdy neplánujete extrahovať. Ani jeden z týchto dôvodov si nevyžaduje nič zložité: prečítajte počet, skontrolujte názvy a rozhodnite sa, ako naložíte s bajtmi

Vlastnosti príloh zobrazené v tomto článku sú súčasťou produktu PDFium Component pre Delphi a C++Builder