65.9K
CodeProject 正在变化。 阅读更多。
Home

PDF 表单解析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (50投票s)

2004年8月20日

BSD

7分钟阅读

viewsIcon

686461

downloadIcon

11027

一个用 C#.NET 编写的 PDF 表单解析器。

引言

尽管 PDF 文档最常用于静态内容,但它们也可以用于表示用户可填写的表单,很像 HTML 表单。PDF 表单可以通过获取现有 PDF 文档并使用 Adobe® Acrobat® 等工具在其上放置表单字段来创建。在许多情况下,生成的 PDF 表单由人类用户使用 Adobe Acrobat 等 PDF 查看工具填写。实际数据可以与包含表示的 PDF 分离,使用 FDF 或 XFDF 文件,后者是一种包含特定文档表单字段内容的 XML 格式。通过使用 FDF 或 XFDF,在内容从数据库生成或查询的场景中,可以轻松地以编程方式填写 PDF 表单。

然而,在某些场景中,需要将实际内容整合到 PDF 本身中,以便只包含一个文件,其中既有内容又有表示。本文介绍的小型解析器正是为了实现这一目的,即解析包含表单字段的现有 PDF 文档,以编程方式获取和设置表单字段内容,并将生成的 PDF 文档写回。

背景

PDF 是 Adobe Systems, Inc. 于 1993 年设计的一种专有格式。它源自 Postscript,而 Postscript 又源自 Forth 语言。PDF 规范可在 Adobe 网站上公开获取。

当我第一次尝试以编程方式填写 PDF 表单时,我不知道 PDF 格式是什么样的。所以我只是用文本编辑器打开了一个 PDF 文件,发现内容实际上是人类可读的(或者看起来是这样)。识别表单字段并替换其内容很容易。以下是 PDF 文件中显示文本字段表示方式的摘录:

2774 0 obj
<< 
/Type /Annot 
/Subtype /Widget 
/Rect [ 27.09381 776.96008 194.09021 789.76807 ] 
/F 4 
/P 1996 0 R 
/AP << /N 14 6 R >> 
/DA (/Helv 10 Tf 0 g)
/T (Name)
/FT /Tx 
/Ff 4194304 
/DV (Smith)
/V (Smith)
>> 
endobj

在这里,` /T (Name) ` 毫不奇怪地表示您在 Acrobat 属性对话框中分配给它的字段名称。同样很容易理解括号中的“Smith”字符串代表字段的内容。` /V ` 代表实际值,而 ` /DV ` 代表当字段重置时字段内容恢复的默认值。

如果您将字符串“Smith”替换为“Jones”,您会发现字段内容实际上并没有改变,只有在您单击 Acrobat 中的字段后才会改变。这是因为 Acrobat 不使用表单字段的值进行视觉表示,而是将视觉表示“缓存”在一个由 `/AP` 条目引用的外观流对象中。只有在您单击字段后,Acrobat 才会重新生成外观流,从而重新生成视觉表示。为了解决这个问题,您可以尝试找到外观流并在其中也更改字符串。

但还有更多问题。如果您将“Smith”替换为“Washington”,Acrobat 将报告错误。这是因为 PDF 实际上不是文本格式,而是二进制格式,其中包含一个带有所有对象起始字节偏移量的偏移表。

如果您通过扩展文件中较早的对象来更改对象的偏移量,但不修复偏移表,则文件会损坏。通常 Acrobat 可以修复偏移表中的小错误,所以您通常仍然可以在 Acrobat 中看到一些东西,但显然这不是填写表单字段的正确方法。

解决此问题的一种方法是始终替换相同数量的字符,截断过长的字符串并用空格填充过短的字符串。如果您可以控制 PDF 表单的设计,您可能会选择将每个文本字段的初始内容设置为固定数量的空格字符,这些字符肯定会超出字段框的右边缘。

虽然这些变通方法在某些情况下可能适用,但我发现它们并不令人满意,于是我编写了自己的小型 PDF 解析器。

PDF 解析器

该解析器不是一个功能齐全的 PDF 解析器,而是一个小型、单类的解析器,可以放入任何需要表单字段解析而不是添加大量开销的整个库的项目中。尽管该解析器支持除流之外的所有类型的 PDF 对象,但它只通过查看 `AcroForm` 字典来解析 PDF 文件的表单字段。如果您需要一个功能齐全的 PDF 解析器,您可能需要查看已移植到包括 .NET 在内的多个平台的 iText 库。

该解析器被设计为一个直接的递归下降解析器。由于我们只对表单字段感兴趣,因此解析器首先解析包含所有对象偏移量的交叉引用表,然后找到包含所有表单字段标识符的 `AcroForm` 字典。一旦我们知道所有表单字段的起始和结束偏移量,我们就可以以递归下降的方式解析每个表单字段对象(它们是一种特殊的字典对象)。总而言之,解析整个 PDF 的步骤如下:

  1. 解析交叉引用表,识别所有对象的字节偏移量。
  2. 解析 `AcroForm` 字典对象,识别表单字段对象标识符。
  3. 以递归下降方式解析所有表单字段对象。
这使我们得到一个(C#)对象列表,其内容可以通过编程方式查询和更新。为了写入符合规范的 PDF 文件,我们利用了 PDF 格式的一个特性,该特性提供了 PDF 文档易于扩展的功能。PDF 对象提供了一个简单的版本控制机制,使得可以将 PDF 文件中已包含的对象的较新版本附加到文件中。我们只需写出所有已更改的字段对象,并添加一个更新的交叉引用表,该表链接到旧的交叉引用表。当您更改表单字段并按下“保存”按钮时,Acrobat 本身也使用相同的机制。这就是为什么 PDF 文件会不断变大,尽管您实际上没有添加任何新内容。只有当您执行“另存为”时,Acrobat 才会重新组织 PDF 并消除重复的对象条目。

使用代码

以下示例读取 PDF 文件,解析它,更改表单字段的值,并将更新的 PDF 文件写回。
// read the file and parse it
PdfReader reader = new PdfReader(filename);

// change one text field
try
{
    ((PdfTXField)reader.FieldsByName["Name"]).Text = "Doe";
}
catch
{
}

// write the updated file back out
FileStream fileStream = new FileStream(newFilename, System.IO.FileMode.Create);
reader.WritePdf(fileStream);
fileStream.Close();

字段的大多数属性也可以通过 .NET 中的属性访问,例如:

// a radio button
PdfRadioButtonField f = ...;
// set the selected button, "Off" means just that.
f.SelectedItem = "MasterCard";
// one button must be pressed
f.NoToggleToOff = true;

// a check box
PdfCheckBoxField f = ...;
// check it
f.Checked = true;

// a text field
PdfTXField f = ...;
// set the text
f.Text = "Hello, World.";
// mark it as a password field
f.Password = true;

// a combo or list box
PdfCHField f = ...;
// render as combo box
f.Combo = true;
// more than one item is selectable
f.MultiSelect = true;
// select items 1 and 3
f.SetSelectedIndexes(1, 3);

关注点

  • 该解析器可以处理 PDF 参考文档中提供的几乎所有字符串表示形式,即包含转义序列的字面字符串和可能缺少数字的十六进制字符串。它还可以解析 Unicode (UTF-16) 编码的文本字符串。但是,不支持语言检测。字符串始终以字面格式写入。
  • 该解析器支持除签名字段外的所有表单字段类型。支持的类型包括按钮(包括按钮、复选框和单选按钮)、文本和选择。
  • 该解析器目前无法处理线性化 PDF 文件,即在 Acrobat 中使用“优化快速网页视图”选项保存的文件。此外,加密文件无法解析。
  • 对于演示表单,您可能需要下载 Adobe Acrobat 表单示例包,其中包含许多展示 PDF 表单大部分功能的表单。
  • Adobe、Acrobat 和 Acrobat Reader 是 Adobe Systems Incorporated 在美国和/或其他国家/地区的注册商标或商标。

使用的工具

我使用 NUnit 单元测试框架编写了许多单元测试,这些测试已包含在源代码中。

可以使用 NDoc 代码文档生成器从源代码生成类库文档。然后,该文档可以在 Visual Studio.NET 中使用,就像 .NET Framework 类库文档一样。源代码中包含一个适用于 NDoc 的适当配置文件。

NUnit 和 NDoc 都是开源软件。

历史

  • 2004 年 8 月 19 日:版本 1.0。
  • 2004 年 8 月 26 日:版本 1.1。
    • 添加了关于外观流的段落。
  • 2004 年 9 月 25 日:版本 1.2。
    • 现在支持线性化文件。
    • 现在支持继承字段。
    • 使用 NAnt
    • 使用 log4net
  • 2004 年 10 月 01 日:版本 1.3。
    • 修复了一个解析对象的错误(感谢 Eddie Neal 帮助我找到它)。
    • 修复了许多 FxCop 问题,特别是关于命名方面的问题(感谢 Heath Stewart 提醒我)。
© . All rights reserved.