Teknisk artikel

PDF-bilagor i Delphi med PDFium Component: Läs, lägg till, ta bort

PDF-filbilagor lagras i dokumentets inbäddade filträd, en struktur som de flesta visare uppvisar som en gem-panel eller ett sidofält för bilagor. Från Delphi-kod exponerar PDFium Component det trädet genom en liten uppsättning indexerade egenskaper på TPdf: du itererar via heltalsindex, läser namn och byte-laster, skapar nya platser och tar bort befintliga. API-ytan är smal; det finns bara ett fåtal ordningsbegränsningar och en saneringsregel värd att veta innan du skriver produktionskod runt den

Att läsa bilagor från ett öppet dokument

AttachmentCount anger antalet inbäddade filer som dokumentet deklarerar. Den läser direkt från PDFiums underliggande anrop, så den återspeglar bara vad PDF:en faktiskt innehåller. Därifrån returnerar AttachmentName[Index] visningsnamnet som en WString, och Attachment[Index] levererar de råa byten som en TBytes-array. Båda är nollbaserade. Dokumentet måste vara öppet (Pdf.Active = True) innan du frågar efter någon egenskap; att anropa dem på ett stängt dokument ger dig noll eller ett tomt resultat utan något undantag

En sak att tänka på: Attachment[Index] allokerar och returnerar hela filens last vid varje läsning. För ett dokument som bär på en stor inbäddad resurs innebär iteration genom alla bilagor för att bygga en visningslista att du betalar den allokeringskostnaden vid varje anrop. Om du bara behöver namn i visningssyfte, läs AttachmentName först och skjut upp byte-hämtningen tills användaren faktiskt begär 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;

Extrahera en bilaga till disk

Det finns ingen SaveAttachment-hjälpare. Du läser byten och skriver dem var du än behöver, vilket lägger sökvägskonstruktion och sanering helt på din kod. Det spelar roll när bilagenamn kommer från opålitliga dokument. PDF-bilagenamn är strängar lagrade inuti filen; de kan innehålla sökvägsavgränsare, Unicode-lookalikes och andra tecken som kommer att producera oväntade resultat om du skickar dem direkt till TFileStream.Create. Kör alltid namnet genom ExtractFileName innan du bygger någon utdatasökväg, och överväg att avvisa namn som börjar med en punkt eller innehåller tecken utöver vad ditt system förväntar sig

Byte-arrayen som returneras av Attachment[Index] ägs av anroparen. Skriv ut den med en normal TFileStream så är den din att göra vad du vill med, inklusive att inspektera de första byten för att verifiera det faktiska filformatet snarare än att lita på det deklarerade namnet

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;

Lägga till bilagor och tvåstegsskrivningen

Att skapa en bilaga tar två anrop, inte ett. CreateAttachment(Name) registrerar en ny plats i det inbäddade filträdet och returnerar True vid framgång. Den platsen börjar tom. Du tilldelar sedan lasten genom att skriva till Attachment[AttachmentCount - 1], vilket riktar in sig på den senast skapade posten. Om CreateAttachment returnerar False, skapades inte platsen och tilldelningen skulle korrumpera bilagan vid det index som råkar vara sist

Efter att ha modifierat bilagelistan lever ändringarna enbart i minnet. Anropa SaveAs för att skriva en ny fil med det uppdaterade inbäddade filträdet. PDFium Component stöder inte att spara tillbaka till samma fil som för närvarande är öppen, eftersom motorn håller ett läshandtag till källan. Standardmönstret för en uppdatering på plats är att spara till en tillfällig sökväg, stänga dokumentet, ta bort eller byta namn på originalet, och sedan byta namn på temp-filen till dess plats och öppna igen

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;

Typsinformation för bilagor

Utöver namnet och byte-lasten, returnerar AttachmentType[Index] MIME-typsträngen lagrad i PDF:ens inbäddade filordlista, om en sådan spelades in när filen ursprungligen bifogades. Många generatorer lämnar det här fältet tomt eller sätter det till ett generiskt värde som application/octet-stream, så du kan inte förlita dig på det för formatdetektering i en produktions-pipeline. För tillförlitlig identifiering, läs de första byten av lasten och kontrollera efter kända filsignaturer: %PDF för en kapslad PDF, ZIP-lokalfilhuvudet PK\x03\x04 för Office Open XML-dokument, \xD0\xCF\x11\xE0 för äldre compound-file-binärer. Typinformation från ordlistan går bra att exponera i en UI-etikett, men bör inte styra bearbetningsbeslut när du har de faktiska byten tillgängliga

Ta bort bilagor

DeleteAttachment(Index) tar bort posten på den positionen och returnerar True vid framgång. Efter borttagning flyttas de återstående posterna ner, så om du tar bort flera bilagor i en loop måste du iterera från det sista indexet och nedåt, inte framåt, för att undvika att hoppa över poster efter varje flyttning. Ändringen är i minnet tills du anropar SaveAs

Ett vanligt scenario i pipelines för dokumentbearbetning är att rensa bort alla bilagor från en inkommande PDF innan den skickas vidare nedströms, av säkerhets- eller storleksskäl. Räkna en gång före loopen och iterera baklänges:

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

Var PDF-bilagor dyker upp i praktiken

API:et för bilagor fungerar på alla PDF-filer som PDFium kan öppna, men de dokument där du faktiskt stöter på inbäddade filer kretsar kring några specifika fall. PDF/A-3 (ISO 19005-3) tillåter uttryckligen inbäddade filer som följer standarden som en mekanism för att bunta källdata vid sidan av arkivåtergivningen; elektroniska fakturor enligt ZUGFeRD och Factur-X förlitar sig på exakt detta för att bädda in en strukturerad XML-last inuti den mänskligt läsbara PDF-layouten. E-posthärledda PDF-filer bär ibland sina ursprungliga meddelandebilagor vidarebefordrade in i det inbäddade filträdet. Teknisk dokumentation som har sitt ursprung i strukturerade författarsystem buntar ibland stödjande resurser på samma sätt

När din applikation bearbetar inkommande PDF-filer utifrån din organisation är det värt att kontrollera AttachmentCount som en del av dokumentintaget av två oberoende skäl. För det första kan inbäddade filer bära data du vill extrahera och bearbeta, som XML inuti en faktura-PDF. För det andra kan inbäddade filer bära godtyckligt körbart innehåll, så det är viktigt att veta vad som finns närvarande även när du aldrig har för avsikt att extrahera det. Inget av skälen kräver att du gör något komplicerat: läs antalet, kontrollera namnen och bestäm vad du ska göra med byten

Bilageegenskaperna som visas här är en del av PDFium Component för Delphi och C++Builder