将XFA与PDFBox结合使用

8
我想使用PDFBox Java库填写PDF表单。 该PDF表单是使用Adobe Live Designer创建的,因此它使用XFA格式。
我试图寻找有关使用PDFBox填写XFA PDF表单的资源,但迄今为止没有什么运气。我看到API中提供了一个PDAcroForm.setXFA方法,但我不知道如何使用它。
你知道是否可以使用PDFBox填写PDF表单吗? 如果是,是否有代码示例或教程可供参考? 如果不行,有哪些最佳替代方案?
5个回答

8

在分配给我解决问题的时间内,这是我能够管理的最好的结果。我将PDF(在Life Cycle中)保存为优化过的(PDF并不是由我创建的)。这是PDF打开部分,XML复制并保存:

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

然后是设置字段值的方法。我同时设置了XFA和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));
        }
    }
}

这种方式可以运作,但不适用于所有PDF生命周期产生的文档。有些文档会出现“扩展功能不再启用”的警告信息,但仍然可以使用。优化版本是我找到的唯一一个在填写后打开时不会提示消息的版本。

如果不同时填写XFA和Acroform,则无法在所有查看器中使用。


打开保存的文件后没有显示任何值。同时在读取时显示文件结束过早。 - Pramod Waghmare

6
这个问题特别指出主题中的PDFBox库; 您不需要iText,可以使用PDFBox 1.8中提供的PDXFA对象进行XFA操作。
非常感谢Maruan Sahyoun在PDFBox + XFA方面所做的出色工作。
当您删除PDDocument上的所有安全性时,此代码才能正常运行。
它还假定PDXFA中的COS对象是COSStream。 下面是一个简单的示例,它读取xml流并将其写回PDF。
 PDDocument doc = PDDocument.load("filename");
 doc.setAllSecurityToBeRemoved(true);

 PDDocumentCatalog docCatalog = doc.getDocumentCatalog();
 PDAcroForm form = docCatalog.getAcroForm();

 PDXFA xfa = form.getXFA();
 COSBase cos = xfa.getCOSObject();
 COSStream coss = (COSStream) cos;
 InputStream cosin = coss.getUnfilteredStream();
 Document document = documentBuilder.parse(cosin);

 COSStream cosout = new COSStream(new RandomAccessBuffer());
 OutputStream out = cosout.createUnfilteredStream();

 TransformerFactory tFactory = TransformerFactory.newInstance();
 Transformer transformer = tFactory.newTransformer();
 DOMSource source = new DOMSource(xmlDoc);
 StreamResult result = new StreamResult(out);
 transformer.transform(source, result);

 PDXFA xfaout = new PDXFA(cosout);
 form.setXFA(xfaout);

我使用了上面的代码,但有点担心生成的文件非常大。PDF文件最初为647kb。新的PDF文件为14000kb。有人知道如何减小生成的新文件的大小吗?在写回PDF文件时可以设置某种类型的压缩吗? - chamalabey
JammyDodger代码仅展示如何访问XML以及如何将其放回。不展示如何查找字段,如何设置值......DocumentBuilder是javax.xml.parsers.DocumentBuilderFactory获得的javax.xml.parsers.DocumentBuilder,就我所看到的而言。未定义的变量xmlDoc应该是文档(我的猜测)。 - Remi Morin
@JammyDodger 我遇到了类似的问题。我想使用PDFBox读取XFA文件中的内容,但我无法弄清楚如何做到。你能否添加一些相关代码或至少解释上面的代码? - Mayank

1
我不熟悉pdfbox,但是一旦您获得对XFA(XML)DOM的访问权限,您可以使用iText(http://itextpdf.com/)完成此操作。

我最终选择了iText。 - OutOfBound

0

尝试这个,它将合并所有没有XFA和有XFA(仅在使用PDBox时)的PDF。


PDAcroForm form = document.getDocumentCatalog().getAcroForm();
 if(form != null) {
      document.setAllSecurityToBeRemoved(true);
      form.flatten();
      if(form.hasXFA()) {
         form.setXFA(null);
      }
 }                       
 merge.appendDocument(anyPDFDoc, document);

-2

AcroForm 用于具有静态字段的 PDF。如果 PDF 具有 xfa 表单,则可以使用 itext(Java)或 itextsharp(.net)填充您的数据。 XFA 表单的唯一问题是它们无法使用 itext 进行平铺,我发现唯一的平铺方法是使用 bullzip 或类似的 pdf 创建器打开使用 itext 创建的 xfa pdf,并将其通过 bullzip 传递,bullzip 将输出平铺的 pdf 版本。希望这能给您一些想法。

下面的代码只是关于如何填写 xfa 的大致想法。

XfaForm xfa = pdfFormFields.Xfa;
dynamic bytes = Encoding.UTF8.GetBytes("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <form1> <staticform>" + "\r\n<barcode>" + barcode + "</barcode></staticform> <flowForm><Extra>" + Extra + "</Extra></flowForm> </form1>");
MemoryStream ms = new MemoryStream(bytes);
pdfStamper.AcroFields.Xfa.FillXfaForm(ms);

现在你可以使用你创建的XFA PDF并通过Bullzip打印了

const string Printer_Name = "Bullzip PDF Printer";

                    PdfSettings pdfSettings = new PdfSettings();
                    pdfSettings.PrinterName = Printer_Name;
                    pdfSettings.SetValue("Output", flatten_pdf);
                    pdfSettings.SetValue("ShowPDF", "no");
                    pdfSettings.SetValue("ShowSettings", "never");
                    pdfSettings.SetValue("ShowSaveAS", "never");
                    pdfSettings.SetValue("ShowProgress", "no");
                    pdfSettings.SetValue("ShowProgressFinished", "no");
                    pdfSettings.SetValue("ConfirmOverwrite", "no");
                    pdfSettings.WriteSettings(PdfSettingsFileType.RunOnce);
                    PdfUtil.PrintFile(xfa_pdffile, Printer_Name);

输出文件将会是扁平化的PDF。


5
好的,我们总是需要代码。所以如果你能提供的话,请提供。 - j0k
这个问题被标记为Java。Bullzip不可用于Java! - filip-fku

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接