A PDF 可以在其中攜帶其他檔案。它可以是將合約和試算表打包在一起的作品集,或是附帶來源資料的報告。當您在程式碼中提取這些檔案時,您希望每個檔案以作者給予的名稱保存,而不是 attachment1.bin。本教學示範如何使用 Aspose.PDF for .NET 在 C# 中提取 PDF 中的嵌入檔案並保留原始名稱。您將打開 PDF,遍歷其嵌入檔案,使用 FileSpecification.GetFileName 解析每個檔案的名稱,並將內容寫入資料夾。
PDF 在哪裡儲存嵌入檔案的名稱
每個嵌入的檔案皆由檔案規格描述,而檔案規格可以在多個位置記錄檔名。它包含 Unicode 檔名、標準檔名,以及檔案在文件嵌入檔案集合中列出的鍵。PDF 產生器並未始終如一地填寫這些資訊,且有些甚至完全省略檔名。
Aspose.PDF for .NET 提供 FileSpecification.GetFileName 屬性來處理此問題。它會從 Unicode 名稱、常規檔案名稱或集合鍵中解析名稱(以可用者為準),因此即使缺少 Unicode 名稱,仍能取得可用的名稱。您可以為完全沒有名稱的檔案傳遞備用名稱。可選的第二個參數控制在沒有檔案名稱時是否允許空名稱。您只需一次呼叫即可取代自行編寫的讀取 PDF 字典的邏輯。
注意:本文中的示例僅使用回退參數。
Document.EmbeddedFiles 保存文件層級的附件,包括 PDF 投資組合中的檔案。透過檔案附件註解(通常顯示為迴紋針圖示)附加到特定頁面的檔案則儲存在該註解上;提取附加於頁面的檔案 會說明這些情況。
使用 Aspose.PDF 從 PDF 中提取嵌入的檔案
- 一個 .NET 專案。
- Aspose.PDF for .NET 26.9 or later,此版本引入了
FileSpecification.GetFileName。從 NuGet 安裝它:
Install-Package Aspose.PDF
或者使用 .NET CLI:
dotnet add package Aspose.PDF
- 包含嵌入檔案的 PDF。範例使用名為
PDFPortfolio.pdf的投資組合。
如果沒有授權,Aspose.PDF 會以評估模式運行。若要在無評估限制的情況下測試,請申請一個免費臨時授權。
提取每個嵌入的文件及其原始名稱
以下方法會打開 PDF,將每個嵌入的檔案以其解析後的名稱儲存到輸出資料夾,並列印每個寫入檔案的路徑。
using System;
using System.IO;
using Aspose.Pdf;
public static class EmbeddedFileExtractor
{
public static void ExtractEmbeddedFiles(string inputPdf, string outputDir)
{
Directory.CreateDirectory(outputDir);
using (var document = new Document(inputPdf))
{
foreach (FileSpecification fileSpec in document.EmbeddedFiles)
{
// Resolve the stored name, then keep only the file name part.
string fileName = Path.GetFileName(fileSpec.GetFileName("attachment.bin"));
string outputPath = Path.Combine(outputDir, fileName);
using (var output = new FileStream(outputPath, FileMode.Create))
{
fileSpec.Contents.CopyTo(output);
}
Console.WriteLine($"Saved: {outputPath}");
}
}
}
}
使用 PDF 路徑和您想要放置檔案的資料夾呼叫它:
EmbeddedFileExtractor.ExtractEmbeddedFiles("PDFPortfolio.pdf", "ExtractedFiles");
每個步驟的作用
打開文件。 new Document(inputPdf) 載入 PDF。using 區塊在處理完成時釋放檔案句柄,即使拋出例外也會如此。
遍歷附件。 document.EmbeddedFiles 返回每個文檔級嵌入文件的一個 FileSpecification。
解析名稱。 fileSpec.GetFileName("attachment.bin") 會返回儲存的名稱,如果 PDF 未提供則返回 attachment.bin。選擇一個對您的應用程式有意義的備援方案。
剝除資料夾段落。 儲存的名稱可能包含路徑,例如 reports/2026/q3.xlsx,或是手動構造的值,例如 ..\..\startup.bat。將結果傳遞給 Path.GetFileName 會將其縮減為 q3.xlsx 或 startup.bat,因此檔案無法寫入 outputDir 之外的位置。即使您信任今天處理的 PDF,也請保留此步驟。
寫入內容。 fileSpec.Contents 是嵌入檔案資料的串流。CopyTo 將其寫入新檔案,且不進行任何轉換。
預期結果
輸出資料夾包含每個文件層級附件的一個檔案,檔名與其在 PDF 中的存儲名稱相同。對於包含試算表和 Word 文件的投資組合,控制台輸出大致如下:
Saved: ExtractedFiles\budget-2026.xlsx
Saved: ExtractedFiles\service-agreement.docx
檔案名稱取決於您的 PDF。提取的檔案包含相同的嵌入資料,因此每個檔案都會在能處理其格式的應用程式中開啟。
處理重複和不安全的名稱
基本範例有三個在實際 PDF 中會遇到的弱點:
- 兩個附件可以共享相同的名稱,且
FileMode.Create會悄悄覆寫第一個附件。當多個附件沒有名稱且全部回退為attachment.bin時,也會發生相同情況。 - 在某個作業系統上有效的名稱,可能包含在其他作業系統上無效的字元,例如 Windows 上的
:。 - 以路徑分隔符結尾的儲存名稱,會使
Path.GetFileName回傳空字串。
此輔助程式同時處理這三種情況。它會取代無效字元,當名稱為空時使用預設值,且在檔案已存在時添加計數器:
private static string GetSafeOutputPath(string outputDir, string resolvedName, string fallbackName)
{
string fileName = Path.GetFileName(resolvedName);
foreach (char c in Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
if (string.IsNullOrWhiteSpace(fileName))
{
fileName = fallbackName;
}
string baseName = Path.GetFileNameWithoutExtension(fileName);
string extension = Path.GetExtension(fileName);
string outputPath = Path.Combine(outputDir, fileName);
int counter = 1;
while (File.Exists(outputPath))
{
outputPath = Path.Combine(outputDir, $"{baseName} ({counter}){extension}");
counter++;
}
return outputPath;
}
在提取迴圈中,將建立 fileName 和 outputPath 的兩行程式碼替換為:
string outputPath = GetSafeOutputPath(outputDir, fileSpec.GetFileName("attachment.bin"), "attachment.bin");
因為輔助程式會檢查現有檔案,若將提取兩次執行到同一資料夾,會產生編號副本而不是覆寫。如果您希望每次執行都有全新的結果,請先清空資料夾。
提取頁面附加的檔案
如果 EmbeddedFiles 為空,但您在 PDF 檢視器中看到回形針圖示,則該檔案可能是透過頁面上的檔案附件註解附加的。該註解在 File 屬性中保存其自己的 FileSpecification,因此相同的名稱解析和相同的輔助工具會套用。此方法會檢查每一頁,並將每個附加的檔案保存到輸出資料夾:
using Aspose.Pdf.Annotations;
public static void ExtractPageAttachments(string inputPdf, string outputDir)
{
Directory.CreateDirectory(outputDir);
using (var document = new Document(inputPdf))
{
foreach (Page page in document.Pages)
{
foreach (Annotation annotation in page.Annotations)
{
if (annotation is FileAttachmentAnnotation attachment && attachment.File != null)
{
string outputPath = GetSafeOutputPath(outputDir, attachment.File.GetFileName("attachment.bin"), "attachment.bin");
using (var output = new FileStream(outputPath, FileMode.Create))
{
attachment.File.Contents.CopyTo(output);
}
Console.WriteLine($"Saved: {outputPath}");
}
}
}
}
}
要收集 PDF 中的所有附件,請使用相同的輸出資料夾執行兩個方法。如果 PDF 從兩個位置引用相同的檔案,GetSafeOutputPath 會將第二個副本以編號名稱儲存,而不是覆寫第一個。
下一步
若要進一步處理附件,例如在不提取的情況下新增、移除或列出它們,請參閱 Aspose.PDF for .NET documentation。FileSpecification class reference 列出了您可以從每個嵌入檔案讀取的其他名稱和描述屬性。如果 PDF 未如預期提取,請在 Aspose.PDF support forum 發佈範例。
常見問題
FileSpecification.GetFileName 如何選擇名稱? 它會從嵌入檔案的 Unicode 名稱、普通檔案名稱或集合鍵中解析名稱,以先有者為準。如果都不存在,則返回您傳入的備用名稱。此方法於 Aspose.PDF for .NET 26.9 中加入。
這對不是作品集的 PDF 有效嗎? 是的,針對在文件層級附加的檔案。透過檔案附件註解(通常顯示為迴紋針圖示)附加到特定頁面的檔案會儲存在該註解上,因此請從
FileAttachmentAnnotation.File讀取,如在 提取附加到頁面的檔案 中所示。為什麼要將 GetFileName 的結果包裹在 Path.GetFileName 中? PDF 中存儲的名稱由創建文件的人設定,可能包含資料夾路徑段。
Path.GetFileName只保留檔名,因此每個附件都會寫入您的輸出資料夾中。如果兩個附件具有相同的名稱會發生什麼情況?
使用FileMode.Create時,第二個檔案會覆蓋第一個。檢查路徑是否已存在,並添加數字後綴,如處理重複和不安全的名稱所示。我可以在不將附件保存到磁碟的情況下處理它嗎? 是的。
Contents屬性是一個Stream,因此您可以將其複製到MemoryStream、上傳,或傳遞給其他庫,而不是寫入FileStream。
