Tuesday, October 14, 2008

SnTT: Merging web form data with a template PDF

The following was written by my friend Duston Suits. We discussed options and he was kind enough to share his solution and allow me to post it.

The problem

To apply on-line for insurance coverage from my organization, you have to: 1) Fill out the web form, and then 2) print it, sign it and mail it in. The problem is that creating an HTML form that is both useable and (more or less) universally printable is an exercise in futility. As an alternative, we have a PDF form with all the fields defined. If we could take the data from the web form, use it to populate the PDF form, then we can let the Adobe Reader handling printing, giving us a more consistent result with far less effort on our part. Merging a PDF form with an FDF file is a trivial task, the challenge was to get this to happen on a Lotus Domino server in the background.

The solution

The key part of the solution is an open-source (Windows-only) tool called PDFTK. PDFTK is a command-line tool with a number of capabilities, the key one for us was the ability to merge a PDF template file with an FDF data file and save the result in yet a third PDF file. My goal was to run a WebQuerySave agent to gather the data from the web form, write it to an FDF file, execute PDFTK in the background and then return a link to the PDF (or potentially a redirect to the PDF itself) back to the user. The next question was how to write the code to create an FDF file.
My colleague already had code to create an XFDF (FDF using XML) file from a Notes form, but alas, PDFTK couldn’t handle XFDF. Some research found the answer: http://www.tgreer.com/fdfServe.html. FDF files, as it turned out, were simple to create. Here’s the code for the WebQuerySave agent:

Sub Initialize()
Dim s As New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument
Dim docid As String

On Error Goto Errorhandler
Set db = s.CurrentDatabase
Set doc = s.DocumentContext
Call doc.save(True,False)
docid = doc.UniversalID
' Create the PDF File
PDFFileName = createFDF(doc)
Now send a link back to the user along with any other HTML you want.
Print {<a href="/} & PDFFileName & {">Click here to open your application</a><br><br>}
End sub

Function createFDF(doc As NotesDocument)
Dim docID As String
Dim fileName As String
Dim fdfFileName As String
Dim pdfFileName As String
Dim tempFDFPath As String
Dim destPDFPath As String
' Directory where we will write the FDF files that are created.
tempFDFPath = {C:\FDF-TEMP}
' Directory where the PDF files will be placed for the end user
destPDFPath = {C:\notes\data\domino\html}
On Error Goto createFDFError
' Use the docID as a file name to make sure it's unique.
docID = doc.UniversalID
hFile = Freefile()
createFDF = docID+ ".pdf"
' Create the full FDF and PDF file names
fdfFileName = tempFDFPath + {\} + docID + ".fdf"
pdfFileName = destPDFPath + {\} + docID + ".pdf"
Open fdfFileName For Output As #hFile
' Write the first part of the FDF file (the same for all of them)
Call writeFDFHeader(hFile)
' Now iterate through all of the fields on the form, find the ones we want and write the name/value to the FDF file.
Forall item In doc.Items
‘ In the case of our form, all the fields that we use in the PDF start with the letter S.
If Left(item.name, 1) = "S" Then
Print #hFile, {<< /T(} + item.name + {) /V(} + item.text + {) >>}
End If
End Forall
' Now write the FDF Trailer information (also the same for all of them.)
Call writeFDFTrailer(hFile)
Close hfile
' And now kick off a process to combine the FDF and the PDF. Of course hard code the paths at
‘ your own peril.
shellstring = {c:\pdf-template\pdftk c:\pdf-template\stdapp.pdf fill_form } + fdfFileName + { output } + pdfFileName
rc = Shell(shellString)
' and return the name of the file to the calling function.
Exit Function
End function

Sub writeFDFHeader(hFile)
Print #hFile, {%FDF-1.2}
Print #hFile, {%âãÏÓ}
Print #hFile, {1 0 obj}
Print #hFile, {<<}
Print #hFile, {/FDF}
Print #hFile, {<<}
Print #hFile, {/F (/formA.pdf)}
Print #hFile, {/ID [ <826851cbc19b7f5fba86369c981fe040> <159c51c6e0b4814ca2552f89ab9a1ed1> ]}
Print #hFile, {/Fields}
Print #hFile, {[}
End Sub

Sub writeFDFTrailer(hFile)
Print #hFile, {]}
Print #hFile, {>>}
Print #hFile, {>>}
Print #hFile, {endobj}
Print #hFile, {trailer}
Print #hFile, {<< /Root 1 0 R >>}
Print #hFile, {%%EOF}
End Sub

This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

One special note, the WebQuerySave agent must be allowed to perform restricted operations (set on the security tab on the agent properties.) Needless to say error checking is also necessary, your mileage may vary.

Happy forming!

Duston Suits

No comments:

Post a Comment