在 C# 中获取 PDF 表单(AcroForm)中已填写的内容,主要依赖于第三方 PDF 处理库。目前
主流且推荐的方案是使用 iText 7 或 Spire.PDF。以下是基于不同库的具体实现方法及关键注意事项。
方案一:使用 iText 7(推荐,适合 .NET Core/.NET 6+)
iText 7 是目前功能最强大且维护活跃的库,能够很好地处理中文、复杂表单结构及签名域。
1. 安装 NuGet 包
bash
Install-Package itext7
Install-Package itext7.pdfa
2. 核心代码实现
以下代码展示了如何遍历所有表单域并提取其名称和值:
csharp
using iText.Kernel.Pdf;
using iText.Forms;
using iText.Forms.Fields;
using System;
using System.Collections.Generic;
public class PdfFormExtractor
{
public static Dictionary<string, string> ExtractFormData(string pdfPath)
{
var formData = new Dictionary<string, string>();
// 使用 PdfReader 加载文件,确保元数据完整
using (PdfReader reader = new PdfReader(pdfPath))
using (PdfDocument pdfDoc = new PdfDocument(reader))
{
// 获取 AcroForm 对象
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDoc, false);
if (acroForm != null)
{
// 获取所有字段名称
IList<string> fieldNames = acroForm.GetFieldNames();
foreach (string fieldName in fieldNames)
{
PdfFormField field = acroForm.GetField(fieldName);
// 跳过签名域,避免干扰或错误
if (field.GetFieldType().Equals(PdfName.SIG))
continue;
// 获取字段值
string value = field.GetValueAsString();
// 存入字典,key为字段名,value为填写内容
formData[fieldName] = value ?? string.Empty;
}
}
// 重要:必须调用 Close 以确保资源释放和数据完整性
pdfDoc.Close();
}
return formData;
}
}
3. 关键注意事项
中文字体问题:如果提取到的中文显示为乱码或空块,通常是因为 PDF 内部字体嵌入问题。但在读取场景下,
GetValueAsString() 通常能正确返回 Unicode 字符串。如果遇到显示问题,需检查 PDF 阅读器端的字体支持,
而非代码提取逻辑。
字段名区分大小写:PDF 表单字段名是严格区分大小写的,且可能包含复杂的前缀(如 form1.TextField1)。
建议先打印所有 fieldNames 以确认确切名称。
关闭文档:务必调用 pdfDoc.Close() 而不是仅依赖 Dispose,以确保底层流正确刷新。
方案二:使用 Spire.PDF for .NET(易用性高)
Spire.PDF 提供了更面向对象的 API,适合快速开发,尤其是对不同类型的表单控件(文本框、复选框等)有专门的类支持。
1. 安装 NuGet 包
bash
Install-Package Spire.PDF
2. 核心代码实现
csharp
using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
using System.Collections.Generic;
public class SpirePdfExtractor
{
public static List<Dictionary<string, string>> ExtractFormData(string pdfPath)
{
var result = new List<Dictionary<string, string>>();
// 加载 PDF 文档
using (PdfDocument doc = new PdfDocument())
{
doc.LoadFromFile(pdfPath);
// 获取表单对象
PdfFormWidget formWidget = doc.Form as PdfFormWidget;
if (formWidget != null && formWidget.FieldsWidget.List.Count > 0)
{
foreach (PdfField field in formWidget.FieldsWidget.List)
{
var fieldData = new Dictionary<string, string>();
string fieldName = field.Name;
string fieldValue = string.Empty;
// 根据字段类型提取值
if (field is PdfTextBoxFieldWidget textBox)
{
fieldValue = textBox.Text;
}
else if (field is PdfCheckBoxFieldWidget checkBox)
{
fieldValue = checkBox.Checked ? "True" : "False";
}
else if (field is PdfRadioButtonListFieldWidget radio)
{
fieldValue = radio.SelectedValue;
}
else if (field is PdfComboBoxFieldWidget comboBox)
{
fieldValue = comboBox.SelectedValue;
}
else if (field is PdfListBoxFieldWidget listBox)
{
fieldValue = listBox.SelectedValue;
}
fieldData["Name"] = fieldName;
fieldData["Value"] = fieldValue;
result.Add(fieldData);
}
}
}
return result;
}
}
方案三:使用 iTextSharp 5(仅限旧项目维护)
警告:iTextSharp 5 已停止维护,不支持 .NET Core/.NET 6+,且存在已知漏洞。仅建议在遗留的 .NET Framework 项目中暂时使用。
csharp
using iTextSharp.text.pdf;
using System.Collections.Generic;
public class LegacyPdfExtractor
{
public static Dictionary<string, string> ExtractFormData(string pdfPath)
{
var formData = new Dictionary<string, string>();
using (PdfReader reader = new PdfReader(pdfPath))
{
// 获取 AcroFields
AcroFields fields = reader.AcroFields;
// 获取所有字段名
foreach (string fieldName in fields.Fields.Keys)
{
// 获取字段值
string value = fields.GetField(fieldName);
formData[fieldName] = value;
}
}
return formData;
}
}
常见问题与最佳实践
如何处理 XFA 表单?
上述方法主要针对标准的 AcroForm。如果 PDF 使用的是动态 XFA 表单(常见于 Adobe LiveCycle 生成的表单),
iText 7 支持有限,可能需要使用 XfaForm 类进行 XML 解析。Spire.PDF 对 XFA 的支持也相对有限。对于复杂 XFA,
建议先转换为标准 AcroForm 或使用 Adobe SDK。
字段值为空怎么办?
检查字段是否被设置为“只读”。
确认字段名是否正确(使用调试器打印所有字段名)。
某些字段可能没有默认值,用户未填写时即为空字符串。
性能优化
如果需要批量处理大量 PDF,建议复用 PdfReader 实例(线程安全需注意)或使用并行处理(Parallel.ForEach),但需确保每个线程独立加载文档。
安全性
如果 PDF 包含数字签名,提取数据时不要修改文档结构,否则会导致签名失效。上述读取操作均为只读,不会影响签名。
选择哪种方案取决于你的项目环境:
.NET Core / .NET 6+ 新项目:首选 iText 7。
需要简单 API 且预算允许:选择 Spire.PDF。
旧版 .NET Framework 项目:可继续使用 iTextSharp 5,但建议逐步迁移。