Search code examples
formspdfitextxfafoxit-reader

How to fill XFA from using iText so it is Foxit Reader comptible


I used examples available on web to create an application that is able to get xml structure of XFA form and then set it back filled. Important code looks like this:

public void readData(String src, String dest)
        throws IOException, ParserConfigurationException, SAXException,
        TransformerFactoryConfigurationError, TransformerException {
    FileOutputStream os = new FileOutputStream(dest);
    PdfReader reader = new PdfReader(src);
    XfaForm xfa = new XfaForm(reader);
    Node node = xfa.getDatasetsNode();
    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
        if ("data".equals(list.item(i).getLocalName())) {
            node = list.item(i);
            break;
        }
    }

    Transformer tf = TransformerFactory.newInstance().newTransformer();
    tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    tf.setOutputProperty(OutputKeys.INDENT, "yes");
    tf.transform(new DOMSource(node), new StreamResult(os));
    reader.close();
}

public void fillPdfWithXmlData(String src, String xml, String dest)
        throws IOException, DocumentException {

    PdfReader.unethicalreading = true;
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest), '\0', true);
    AcroFields form = stamper.getAcroFields();
    XfaForm xfa = form.getXfa();
    xfa.fillXfaForm(new FileInputStream(xml));
    stamper.close();
    reader.close();
}

When I use it to fill this form: http://www.vzp.cz/uploads/document/tiskopisy-pro-zamestnavatele-hromadne-oznameni-zamestnavatele-verze-2-pdf-56-kb.pdf it works fine and I can see the filled form in Acrobat Reader. However, if I open the document in Foxit Reader I see a blank form (tested in latest version and 5.x version).

I tried to play a little bit with it and got these XfaForm(...).getDomDocument() data:

Filled by Acrobat Reader: http://pastebin.com/kXKyh9EM
Filled by Foxit Reader: http://pastebin.com/tiZ7EmfE
Filled by iText: http://pastebin.com/tTKLMERC
Filled by Foxit Reader after a fill by iText: http://pastebin.com/Uuq0jS4b

The field which was filled is . Is it possible to use iText in a way that it works even with Foxit Reader (and the XFA signature stays?)


Solution

  • In your Filled by iText example there's a superfluous <xfa:data> element:

    <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
      <xfa:data>
        <xfa:data>
          <HOZ>
            <!-- rest of your data here -->
          </HOZ>
        </xfa:data>
      </xfa:data>
      <!-- data description -->
    </xfa:datasets>
    

    This is because the fillXfaForm() method of XfaForm expects the XML data without <xfa:data> as the root element. So your XML data should just look like:

    <HOZ>
      <!-- rest of your data here -->
    </HOZ>
    

    I see that your readData() method that extract the existing form data including the <xfa:data> element:

    <xfa:data>
      <HOZ>
        <!-- rest of your data here -->
      </HOZ>
    </xfa:data>
    

    Stripping the outer element should fix your problem. For example:

    XfaForm xfa = new XfaForm(reader);
    Node node = xfa.getDatasetsNode();
    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
        if ("data".equals(list.item(i).getLocalName())) {
            node = list.item(i);
            break;
        }
    }
    // strip <xfa:data>
    node = node.getFirstChild();
    
    // Transformer code here