Teknisk artikkel

PDF-vedlegg i Delphi med PDFium-komponent: Les, Legg til, Slett

PDF-filvedlegg lagres i dokumentets innebygde-filtre (embedded-file tree), en struktur som de fleste visningsprogrammer viser frem (surfaces) som et binders-panel eller en vedlegg-sidefelt. Fra Delphi-kode eksponerer PDFium-komponenten det treet gjennom et lite sett med indekserte egenskaper på TPdf: du itererer med heltallsindeks, leser navn og byte-nyttelaster (byte payloads), oppretter nye plasser (slots), og sletter eksisterende. API-flaten (API surface) er smal; det er bare et par rekkefølgebegrensninger og én saneringsregel (sanitization rule) verdt å vite om før du skriver produksjonskode rundt det

Lese vedlegg fra et åpent dokument

AttachmentCount gir antallet innebygde filer dokumentet erklærer. Den leser direkte fra PDFiums underliggende oppkall, så den reflekterer bare det PDF-en faktisk inneholder. Derfra returnerer AttachmentName[Index] visningsnavnet som en WString, og Attachment[Index] leverer de rå bytene som en TBytes-array. Begge er null-baserte. Dokumentet må være åpent (Pdf.Active = True) før du spør (query) etter noen av egenskapene; å kalle dem på et lukket dokument gir deg null eller et tomt resultat uten noe unntak (exception)

Én ting å ha i bakhodet: Attachment[Index] tildeler (allocates) og returnerer den fulle filnyttelasten på hver lesing. For et dokument som bærer en stor innebygd ressurs, vil det å iterere gjennom alle vedlegg for å bygge en visningsliste bety at du betaler den tildelingskostnaden (allocation cost) på hvert oppkall. Hvis du bare trenger navn for visningsformål, les AttachmentName først og utsett (defer) bytehentingen (byte fetch) inntil brukeren faktisk ber om filen

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;

Trekke ut (Extracting) et vedlegg to disk

Det finnes ingen SaveAttachment-hjelper. Du leser bytene og skriver dem ut dit du trenger, noe som legger stikonstruksjon (path construction) og sanering fullt og helt på koden din. Det har betydning når vedleggsnavn kommer fra upålitelige dokumenter. PDF-vedleggsnavn er strenger lagret inni filen; de kan inneholde stiseparatorer (path separators), Unicode-lookalikes, og andre tegn som vil produsere uventede resultater hvis du sender dem direkte to TFileStream.Create. Kjør alltid navnet gjennom ExtractFileName før du bygger noen utdatasti (output path), og vurder å avvise navn som starter med et punktum eller inneholder tegn utenfor det systemet ditt forventer

Byte-arrayen returnert av Attachment[Index] er eiet av oppringeren (caller-owned). Skriv den ut med en vanlig TFileStream og den er din til å gjøre med som du vil, inkludert å inspisere de første bytene for å verifisere det faktiske filformatet fremfor å stole på det erklærte navnet

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;

Legge til vedlegg og to-trinns skrivingen

Å opprette et vedlegg krever to kall, ikke ett. CreateAttachment(Name) registrerer en ny plass (slot) i innebygde-filtreet (embedded-file tree) og returnerer True ved suksess. Den plassen starter tom. Deretter tilordner du nyttelasten (payload) ved å skrive til Attachment[AttachmentCount - 1], rettet mot den sist opprettede oppføringen (entry). Hvis CreateAttachment returnerer False, ble ikke plassen opprettet, og tilordningen ville korrumpere (corrupt) vedlegget ved uansett indeks som tilfeldigvis er sist

Etter modifisering av vedleggslisten, lever endringer bare i minnet. Kall SaveAs for å skrive en ny fil med det oppdaterte innebygde-filtreet. PDFium-komponenten støtter ikke lagring tilbake til den samme filen som for øyeblikket er åpen, fordi motoren holder et lesehåndtak (read handle) til kilden. Standardmønsteret for en oppdatering på stedet (in-place update) er å lagre til en midlertidig sti (temporary path), lukke dokumentet, slette eller gi nytt navn til originalen, og deretter gi nytt navn til den midlertidige filen slik at den kommer i posisjon og gjenåpne den

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;

Vedleggstype-informasjon

Utover navnet og byte-nyttelasten, returnerer AttachmentType[Index] MIME-typestrengen som er lagret i PDF-ens innebygde-fil-ordbok (embedded-file dictionary), hvis en ble registrert da filen opprinnelig ble lagt ved. Mange generatorer lar dette feltet stå tomt eller setter det til en generisk verdi som application/octet-stream, så du kan ikke stole på det for formatdeteksjon i en produksjons-arbeidsflyt. For pålitelig identifikasjon, les de første bytene av nyttelasten og sjekk etter kjente filsignaturer: %PDF for en innkapslet (nested) PDF, ZIP-lokal-fil-headeren PK\x03\x04 for Office Open XML-dokumenter, \xD0\xCF\x11\xE0 for eldre sammensatt-fil (compound-file) binærfiler. Typeinformasjon fra ordboken er fint å vise frem i en UI-etikett (UI label), men bør ikke drive behandlingsbeslutninger når du har de faktiske bytene tilgjengelig

Slette vedlegg

DeleteAttachment(Index) fjerner oppføringen på den posisjonen og returnerer True ved suksess. Etter sletting vil de gjenværende oppføringene skifte ned, så hvis du sletter flere vedlegg i en løkke, må du iterere fra den siste indeksen og nedover, ikke forover, for å unngå å hoppe over oppføringer etter hvert skift. Endringen er i minnet inntil du kaller SaveAs

Et vanlig scenario i dokumentbehandlings-arbeidsflyter er å fjerne alle vedlegg fra en innkommende PDF før den sendes videre, av sikkerhets- eller størrelsesgrunner. Tell én gang før løkken og iterer baklengs (in reverse):

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

Hvor PDF-vedlegg dukker opp i praksis

Vedlegg-API-et fungerer på enhver PDF som PDFium kan åpne, men dokumentene der du faktisk støter på innebygde filer, grupperer seg rundt noen få spesifikke tilfeller. PDF/A-3 (ISO 19005-3) tillater eksplisitt samsvarende (conforming) innebygde filer som en mekanisme for å bunte (bundling) kildedata sammen med arkiv-gjengivelsen; ZUGFeRD og Factur-X elektroniske fakturaer baserer seg på akkurat dette for å bygge inn en strukturert XML-nyttelast inni det menneskelesbare (human-readable) PDF-oppsettet (PDF layout). E-post-avledede PDF-er bærer noen ganger med seg sine opprinnelige meldingsvedlegg videresendt inn i innebygde-filtreet. Teknisk dokumentasjon som stammer fra strukturerte forfattersystemer, bunter av og til støtteressurser på samme måte

Når applikasjonen din behandler innkommende PDF-er fra utenfor organisasjonen din, er det verdt å sjekke AttachmentCount som en del av dokumentinntaket (document intake) av to uavhengige grunner. For det første kan innebygde filer bære med seg data du ønsker å trekke ut og behandle, for eksempel XML inni en faktura-PDF. For det andre kan innebygde filer bære med seg vilkårlig kjørbart innhold, så å vite hva som er til stede har betydning selv når du aldri har til hensikt å trekke det ut. Ingen av grunnene krever at du gjør noe komplisert: les antallet, sjekk navnene, og bestem deg for hva du skal gjøre med bytene

Vedlegg-egenskapene vist her er en del av PDFium-komponenten for Delphi og C++Builder