如何使用 iTextSharp 正确填写 XFA 表单数据以允许在 Acrobat XI 中编辑和保存结果

Posted

技术标签:

【中文标题】如何使用 iTextSharp 正确填写 XFA 表单数据以允许在 Acrobat XI 中编辑和保存结果【英文标题】:How to correctly fill in XFA form data using iTextSharp to allow editing and saving result in Acrobat XI 【发布时间】:2014-02-21 21:55:41 【问题描述】:

我有一个应用程序,我正在使用 iTextSharp 填充 pdf 表单。

    /// <summary>
    /// Imports XFA Data into a new PDF file.
    /// </summary>
    /// <param name="pdfTemplate">A PDF File with an unpopulated form.</param>
    /// <param name="xmlFormData">XFA form data in XML format.</param>
    /// <returns>a memorystream containing the new PDF file.</returns>
    public static void XFAImport(System.IO.Stream pdfTemplate, System.IO.Stream xmlFormData, System.IO.Stream outputStream)
    
        using (iTextSharp.text.pdf.PdfReader reader = new iTextSharp.text.pdf.PdfReader(pdfTemplate))
        
            using (iTextSharp.text.pdf.PdfStamper stamper = new iTextSharp.text.pdf.PdfStamper(reader, outputStream))
            
                stamper.Writer.CloseStream = false;
                stamper.AcroFields.Xfa.FillXfaForm(xmlFormData);
            
        
    

上述代码采用未填写的pdf表格和xml数据写入outputStream,然后将其保存到文件中。

当您在 Adob​​e 中打开文件时,您会看到正确填写的表单数据。但是,如果您随后从 Acrobat XI 保存该文件,然后重新打开它,您导入的数据将不再可见。

我不认为问题出在我正在导入的 XML 上,因为如果我不使用 iTextShart,而是使用 Acrobat XI 的“工具/表单/更多表单选项/导入数据”。生成的文件能够正确保存和重新打开。

我的问题是:

我是否正确使用了上面的 PdfStamper?

我可以采取任何步骤来正确保存生成的文件吗?

PS。我注意到,在使用 Acrobat XI 重新保存输出 pdf 文件后,生成的文件与原始文件基本相同,但在末尾插入了额外的 11k 数据。

输出pdf文件结束:

trailer
<</Size 51/Root 14 0 R/Info 3 0 R/ID [<56549fdaf0c5ab4e9321d77f406e6455><5b60738018e0cdac94c6d1b924fc8bed>]>>
%iText-5.4.4
startxref
529008
%%EOF

在 Acrobat XI 中保存后添加了更多数据:

trailer
<</Size 51/Root 14 0 R/Info 3 0 R/ID [<56549fdaf0c5ab4e9321d77f406e6455>         <5b60738018e0cdac94c6d1b924fc8bed>]>>
%iText-5.4.4
startxref
529008
%%EOF
3 0 obj
<</CreationDate(D:20100120124725-05'00')/Creator(Adobe LiveCycle Designer ES 8.2)/ModDate(D:20140221145558-06'00')/Producer(Adobe LiveCycle Designer ES 8.2; modified using iTextSharp’ 5.4.4 ©2000-2013 1T3XT BVBA \(AGPL-version\))>>
endobj
4 0 obj
<</Length 3261/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>

/*more data excluded*/

【问题讨论】:

【参考方案1】:

不,你没有正确使用PdfStamper

阅读器启用是使用数字签名实现的(它需要来自 Adob​​e 的私钥)。当您使用“标准方式”填写表格时,您会破坏该签名。您需要在追加模式下填写表格。

我在我的书的第 8.7.2 节中对此进行了解释,题为“使用 iText 填写支持阅读器的表单”(这让我感到震惊,为什么没有人在提出问题之前阅读文档;有人想知道为什么有人甚至费心写作一本书)。您可以在此处找到本节附带的示例:ReaderEnabledForm

C#版本可以在对应章节on SourceForge中找到:

底线:你需要更换

new iTextSharp.text.pdf.PdfStamper(reader, outputStream)

new iTextSharp.text.pdf.PdfStamper(reader, outputStream, '\0', true)

在这种情况下,您的更改将附加在%%EOF 标记之后,并且 Adob​​e 应用的数字签名不会被破坏。

【讨论】:

【参考方案2】:

感谢您的提示。 这就是我们最终要做的事情(VB.NET):

Public Shared Sub XFAImport(pdfTemplate As System.IO.Stream, xmlFormData As System.IO.Stream, outputStream As System.IO.Stream)
    ' Imports XFA Data into a new PDF file.
    ' pdfTemplate is PDF File with an unpopulated form
    ' xmlFormData is an XFA form data in XML format (the data we wish to enter)
    ' We get a memorystream containing the new PDF file

    Dim reader As New pdf.PdfReader(pdfTemplate)
    PdfReader.unethicalreading = True ' Allow reading a PDF file that is protected by a password

    Using reader
        Using stamper As New iTextSharp.text.pdf.PdfStamper(reader, outputStream, "\0", True)
            stamper.Writer.CloseStream = False
            stamper.AcroFields.Xfa.FillXfaForm(xmlFormData)
        End Using
    End Using
End Sub


Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim strErr As String = ""

    Dim afto22pdf As String = Server.MapPath("../AFTO22/afto22_protected.pdf")
    Dim newXml As String = Server.MapPath("../AFTO22/newxml1.xml")
    Dim newAfto22pdf As String = Server.MapPath("../AFTO22/newAfto22_protected.pdf")


    Dim pdfTemplate As New FileStream(afto22pdf, FileMode.Open, FileAccess.Read)
    Dim xmlFormData As New FileStream(newXml, FileMode.Open, FileAccess.Read)
    Dim outputStream As New FileStream(newAfto22pdf, FileMode.Create, FileAccess.Write)
    Try
        XFAImport(pdfTemplate, xmlFormData, outputStream)
    Catch ex As Exception
        strErr = "Error detected: " & ex.Message
    End Try

    Label1.Text = strErr.ToString

    outputStream.Close()
    pdfTemplate.Close()
    xmlFormData.Close()
    outputStream = Nothing
    pdfTemplate = Nothing
    xmlFormData = Nothing
End Sub

【讨论】:

【参考方案3】:

实际上,上面的代码在我们以编程方式填充其中一部分之后,需要手动在表单中输入数据的人造成了一些问题。我们的 XFA 表单有大约 10 个步骤,我们只填充前 2 个步骤。尝试对后续步骤进行数字签名的人看到一条错误消息,指出“dataModel 没有方法‘克隆’。” 无论如何,我们最终直接填充了表单字段,而无需使用外部 XML。这解决了我们的问题。

Try
   Dim filename As String = Server.MapPath("../AFTO22/Afto22_populated.pdf")
   Dim pdfReader As New PdfReader(Server.MapPath("~/AFTO22/afto22.pdf"))
   pdfReader.unethicalreading = True

   Using stream As New FileStream(filename, FileMode.Create)
      Dim pdfStamper As New PdfStamper(pdfReader, stream, "\0", True)
      Dim formFields As AcroFields = pdfStamper.AcroFields
      formFields.SetField("FIELD1", "My Name")
      formFields.SetField("FIELD5", "My Rank")

      pdfStamper.FormFlattening = False
      pdfStamper.Close()
   End Using
Catch ex As Exception
   Label1.Text = ex.Message
End Try

【讨论】:

以上是关于如何使用 iTextSharp 正确填写 XFA 表单数据以允许在 Acrobat XI 中编辑和保存结果的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iTextSharp 中填写 PDF 表单并支持多种语言?

从 PDF 中提取 xdp 或 xfa

iTextSharp 填充 Pdf 表单图像字段

使用 iTextSharp 将多个页面添加到 pdf 表单

不正确的字符串值:第 1 行的列 'VARIABLE_VALUE' 的 '\xD6\xD0\xB9\xFA\xB1\xEA...' [重复]

如何以编程方式填充现有的 PDF 文档 [关闭]