PDFの添付ファイルは、ドキュメントの組み込みファイルツリーに格納されます。この構造は、ほとんどのビューアでクリップのマークが付いたパネルや添付ファイルサイドバーとして表示されます。Delphiのコードから見ると、PDFium ComponentはTPdfのインデックス付きプロパティの小さなセットを介してそのツリーを公開します。これにより、整数のインデックスで反復処理し、ファイル名や生のバイトデータを読み取り、新しいスロットを作成し、既存のものを削除することができます。API自体は小規模であり、本番環境のコードを作成する前に知っておくべきいくつかの順序の制約と1つのサニタイズルールがあるだけです
開いているドキュメントからの添付ファイルの読み取り
AttachmentCountは、ドキュメントが宣言している組み込みファイルの数を返します。PDFiumの内部の呼び出しから直接読み取るため、PDFが実際に含んでいるデータのみを反映します。そこから、AttachmentName[Index]は表示名をWStringとして返し、Attachment[Index]は生のバイトデータをTBytes配列として提供します。どちらも0から始まるインデックスです。いずれのプロパティをクエリする前にも、ドキュメントが開いている(Pdf.Active = True)必要があります。閉じたドキュメントに対して呼び出した場合、例外は発生せず、単に0または空の結果が返されます
注意すべき点として、Attachment[Index]は読み取りを行うたびにファイル全体のメモリを割り当ててデータを返します。大きな埋め込みアセットを含むドキュメントの場合、表示リストを構築するためにすべての添付ファイルを反復処理すると、呼び出しごとにそのメモリ割り当てコストが発生します。表示用の名前だけが必要な場合は、まずAttachmentNameを読み取り、ユーザーが実際にファイルを要求するまでバイトデータの取得を遅延させてください
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;
添付ファイルのディスクへの抽出
SaveAttachmentのようなヘルパー関数は存在しません。バイトデータを読み取って必要な場所に書き込むため、パスの構築とサニタイズは完全にコードに依存します。これは、信頼できないドキュメントから添付ファイル名を取得する場合に重要になります。PDFの添付ファイル名はファイル内に格納されている文字列であり、パス区切り文字やUnicodeの類似文字、あるいはTFileStream.Createに直接渡すと予期しない結果をもたらす他の文字が含まれている可能性があります。出力パスを構築する前に、必ず名前をExtractFileNameに通してください。また、ドットで始まる名前やシステムの想定外の文字を含む名前を拒否することも検討してください
Attachment[Index]によって返されるバイト配列の所有権は呼び出し側にあります。通常のTFileStreamを使用して書き出せば、宣言された名前を信用する代わりに、最初の数バイトを検査して実際のファイル形式を検証するなど、必要に応じて自由に処理できます
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;
添付ファイルの追加と2ステップの書き込み
添付ファイルの作成には、1回ではなく2回の呼び出しが必要です。CreateAttachment(Name)は組み込みファイルツリーに新しいスロットを登録し、成功するとTrueを返します。このスロットは最初は空です。次に、直前に作成されたエントリを対象として、Attachment[AttachmentCount - 1]に書き込むことでペイロードを割り当てます。CreateAttachmentがFalseを返した場合、スロットは作成されておらず、この代入を行うと、たまたま最後にあるインデックスの添付ファイルを破損させてしまいます
添付ファイルリストを変更した時点では、変更はメモリ内のみに保持されます。更新された組み込みファイルツリーを含む新しいファイルを書き出すには、SaveAsを呼び出します。PDFium Componentは、エンジンが元のファイルへの読み取りハンドルを保持しているため、現在開いているのと同じファイルへの上書き保存をサポートしていません。インプレース(上書き)更新の標準的なパターンは、一時パスに保存し、ドキュメントを閉じてから、オリジナルを削除またはリネームし、一時ファイルを元の位置に移動させて再オープンするという方法です
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;
添付ファイルのタイプ情報
ファイル名とバイトのペイロードに加えて、AttachmentType[Index]は、ファイルが最初添付された際にPDFの組み込みファイル辞書に格納されたMIMEタイプ文字列があればそれを返します。多くのジェネレーターは、このフィールドを空のままにするか、application/octet-streamのような汎用的な値に設定するため、本番のデータ処理パイプラインにおける形式検出にこれを信頼することはできません。信頼性の高い識別を行うには、ペイロードの最初の数バイトを読み取り、既知のファイル署名を確認してください。ネストされたPDFの場合は%PDF、Office Open XMLドキュメントの場合はZIPローカルファイルヘッダーPK\x03\x04、レガシーの複合ファイルバイナリの場合は\xD0\xCF\x11\xE0です。辞書から取得したタイプ情報は、UIラベルに表示するには問題ありませんが、実際のバイトデータが利用可能である場合には、それを処理の決定基準にすべきではありません
添付ファイルの削除
DeleteAttachment(Index)はその位置のエントリを削除し、成功するとTrueを返します。削除後、残りのエントリは下にシフトされるため、ループで複数の添付ファイルを削除する場合は、各シフト後のエントリのスキップを避けるために、最初のインデックスからではなく最後のインデックスから逆方向に反復処理する必要があります。この変更は、SaveAsを呼び出すまでメモリ上にのみ留まります
ドキュメント処理パイプラインにおける一般的なシナリオは、セキュリティやサイズ制限の観点から、受信したPDFを下流の処理に渡す前にすべての添付ファイルを取り除くことです。ループの前に一度数をカウントし、逆方向に処理を行います
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
実務におけるPDF添付ファイルの出現ケース
添付ファイル用APIはPDFiumが開くことのできる任意のPDFで動作しますが、実際に組み込みファイルに遭遇するドキュメントは、いくつかの特定のユースケースに集中しています。PDF/A-3(ISO 19005-3)は、アーカイブ表現の隣にソースデータをバンドルするための仕組みとして、準拠した組み込みファイルを明示的に許可しています。ZUGFeRDおよびFactur-Xの電子請求書は、まさにこの仕組みを利用して、人間が読めるPDFレイアウトの内部に構造化されたXMLペイロードを埋め込んでいます。電子メールに由来するPDFは、元のメッセージの添付ファイルを組み込みファイルツリーに转送して保持していることがあります。構造化された文書作成システムから生成された技術ドキュメントも、同様の方法でサポートアセットをバンドルしている場合があります
組織外から受信したPDFをアプリケーションで処理する場合、ドキュメントの受け入れ処理の一環としてAttachmentCountを確認する価値があります。これには2つの独立した理由があります。第一に、組み込みファイルには、請求書PDF内のXMLのように、抽出して処理したいデータが含まれている可能性があるためです。第二に、組み込みファイルには任意の実行可能なコンテンツが含まれている可能性があるため、抽出するつもりがなくても、何が存在するかを知ることは重要です。どちらの理由においても、複雑な処理を行う必要はありません。数を確認し、名前をチェックし、バイトデータをどのように処理するかを決定するだけです
ここに記載されている添付ファイル用のプロパティは、DelphiおよびC++Builder用のPDFium Componentに含まれています