Combining XFA with PDFBox

OutOfBound picture OutOfBound · May 10, 2012 · Viewed 9.1k times · Source

I would like to fill a PDF form with the PDFBox java library. The PDF form is created with Adobe Live Designer, so it uses the XFA format.

I try to find resources about filling XFA PDF forms with PDFBox, but i haven't any luck so far. I saw that a PDAcroForm.setXFA method is available in the API, but i don't see how to use it.

Do you know if it is possible to fill a PDF Form with PDFBox ? If yes, is there anywhere a code sample or a tutorial to achieve this ? If no, what are the best alternatives to achieve this ?

Answer

Remi Morin picture Remi Morin · Nov 28, 2014

This it the best I was able to manage in the time I was allocated on the problem. I get the pdf saved (in Life Cycle) as optimized (I'm not the one doing the pdf). This is the PDF openning part, XML duplication and then saving:

    PDDocument document = PDDocument.load(fileInputStream);
    fileInputStream.close();
    document.setAllSecurityToBeRemoved(true);

    Map<String, String> values = new HashMap<String, String>();
    values.put("variable_name", "value");


    setFields(document, values); // see code below

    PDAcroForm form = document.getDocumentCatalog().getAcroForm();
    Document documentXML = form.getXFA().getDocument();

    NodeList dataElements = documentXML.getElementsByTagName("xfa:data");
    if (dataElements != null) {
        for (int i = 0; i < dataElements.getLength(); i++) {
            setXFAFields(dataElements.item(i), values);
        }
    }

    COSStream cosout = new COSStream(new RandomAccessBuffer());

    TransformerFactory.newInstance().newTransformer()
            .transform(new DOMSource(documentXML), new StreamResult(cosout.createUnfilteredStream()));

    form.setXFA(new PDXFA(cosout));

    FileOutputStream fios = new FileOutputStream(new File(docOut + ".pdf"));
    document.save(fios);
    document.close();
    try {
        fios.flush();
    } finally {
        fios.close();
    }

then the methods who set values for fields. I set both the XFA and the AcroForm:

public void setXFAFields(Node pNode, Map<String, String> values) throws IOException {
    if (values.containsKey(pNode.getNodeName())) {
        pNode.setTextContent(values.get(pNode.getNodeName()));
    } else {
        NodeList childNodes = pNode.getChildNodes();
        if (childNodes != null) {
            for (int i = 0; i < childNodes.getLength(); i++) {
                setXFAFields(childNodes.item(i), values);
            }
        }
    }
}

public void setFields(PDDocument pdfDocument, Map<String, String> values) throws IOException {

    @SuppressWarnings("unchecked")
    List<PDField> fields = pdfDocument.getDocumentCatalog().getAcroForm().getFields();
    for (PDField pdField : fields) {
        setFields(pdField, values);
    }
}

private void setFields(PDField field, Map<String, String> values) throws IOException {
    List<COSObjectable> kids = field.getKids();
    if (kids != null) {
        for (COSObjectable pdfObj : kids) {
            if (pdfObj instanceof PDField) {
                setFields((PDField) pdfObj, values);
            }
        }
    } else {
        // remove the [0] from the name to match values in our map
        String partialName = field.getPartialName().replaceAll("\\[\\d\\]", "");
        if (!(field instanceof PDSignatureField) && values.containsKey(partialName)) {
            field.setValue(values.get(partialName));
        }
    }
}

This work, but not for all "kind" of PDF life Cycle produce, some got a warning message about "extended fonction" not enabled anymore but still work. The optimize version is the only one I found who don't prompt message when openned after being filled.

I fill the XFA and the Acroform otherwise it don't work in all viewer.