How to correctly fill in XFA form data using iText

2020-05-01 08:54发布

I have an application that I'm using the populate a pdf form using iTextSharp.

    /// <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);
            }
        }
    }

The above code takes a pdf form that is not filled out and xml data and writes to the outputStream which is then saved to a file.

When you open the file in Adobe you see the form data filled out correctly. However if you then save that file from Acrobat XI, and then reopen it, the data that you imported is no longer visible.

I don't believe the problem is with the XML that I am importing, because if instead of using iTextShart, I instead use Acrobat XI's "tools/form/more form options/import data". The resulting file is able to be saved and reopened correctly.

My question is:

Am I using the PdfStamper correctly above?

Are there any steps I can take to make the resulting file save correctly?

PS. I've noticed that after resaving the output pdf file using Acrobat XI that the resulting file is mostly identical to the original, however it has an additional 11k of data inserted at the end.

end of the output pdf file:

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

After saving in Acrobat XI is has more data added to it:

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*/

3条回答
一纸荒年 Trace。
2楼-- · 2020-05-01 09:27

No, you are not using PdfStamper correctly.

Reader enabling is achieved using a digital signature (it requires a private key from Adobe). When you fill out the form using the "standard way", you break that signature. You need to fill out the form in append mode.

I've explained this in section 8.7.2 of my book, entitled "Filling out Reader-enabled forms using iText" (it beats me why nobody ever reads the documentation before asking a question; one wonders why one even bothers writing a book). You can find the example that comes with this section here: ReaderEnabledForm

You can find the C# version in of the corresponding chapter on SourceForge:

Bottom line: you need to replace

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

with

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

In this case, your changes will be appended after the %%EOF marker and the digital signature applied by Adobe won't be broken.

查看更多
Root(大扎)
3楼-- · 2020-05-01 09:38

Actually, the code above caused some problems for people who needed to enter data in the form manually AFTER we populated part of it programmatically. Our XFA form has some 10 steps, where we populate only the first 2 steps. People trying to digitally sign later steps saw an error message, stating "dataModel does not have a method 'clone'." Anyhow, we ended up populating the form fields directly, skipping the need to use an external XML. This solved our problems.

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

查看更多
男人必须洒脱
4楼-- · 2020-05-01 09:47

Thanks for the tips. This is what we ended up doing (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
查看更多
登录 后发表回答