PDFbox表示PDDocument在未关闭时关闭

Ken*_*all 1 java treemap pdfbox

我正在尝试使用PDFbox填充重复的表单。我正在使用TreeMap并使用单个记录填充表单。pdf格式的格式是在第一页上列出了六个记录,在第二页上插入了一个静态页。(对于大于六个记录的TreeMap,将重复此过程)。我得到的错误特定于TreeMap的大小。这就是我的问题。我不知道为什么当我用超过35个条目填充TreeMap时收到此警告:

2018年4月23日2:36:25 org.apache.pdfbox.cos.COSDocument最终定稿警告:警告:您尚未关闭PDF文档

public class test {
    public static void main(String[] args) throws IOException,         IOException {
    // TODO Auto-generated method stub
    File dataFile = new File("dataFile.csv");
    File fi = new File("form.pdf");
    Scanner fileScanner = new Scanner(dataFile);
    fileScanner.nextLine();
    TreeMap<String, String[]> assetTable = new TreeMap<String, String[]>();
    int x = 0;
    while (x <= 36) {
        String lineIn = fileScanner.nextLine();
        String[] elements = lineIn.split(",");
        elements[0] = elements[0].toUpperCase().replaceAll(" ", "");
        String key = elements[0];
        key = key.replaceAll(" ", "");
        assetTable.put(key, elements);
        x++;
    }
    PDDocument newDoc = new PDDocument();
    int control = 1;
    PDDocument doc = PDDocument.load(fi);
    PDDocumentCatalog cat = doc.getDocumentCatalog();
    PDAcroForm form = cat.getAcroForm();
    for (String s : assetTable.keySet()) {
        if (control <= 6) {
            PDField IDno1 = (form.getField("IDno" + control));
            PDField Locno1 = (form.getField("locNo" + control));
            PDField serno1 = (form.getField("serNo" + control));
            PDField typeno1 = (form.getField("typeNo" + control));
            PDField maintno1 = (form.getField("maintNo" + control));
            String IDnoOne = assetTable.get(s)[1];
            //System.out.println(IDnoOne);
            IDno1.setValue(assetTable.get(s)[0]);
            IDno1.setReadOnly(true);
            Locno1.setValue(assetTable.get(s)[1]);
            Locno1.setReadOnly(true);
            serno1.setValue(assetTable.get(s)[2]);
            serno1.setReadOnly(true);
            typeno1.setValue(assetTable.get(s)[3]);
            typeno1.setReadOnly(true);
            String type = "";
            if (assetTable.get(s)[5].equals("1"))
                type += "Hydrotest";
            if (assetTable.get(s)[5].equals("6"))
                type += "6 Year Maintenance";
            String maint = assetTable.get(s)[4] + " - " + type;
            maintno1.setValue(maint);
            maintno1.setReadOnly(true);
            control++;
        } else {
            PDField dateIn = form.getField("dateIn");
            dateIn.setValue("1/2019 Yearlies");
            dateIn.setReadOnly(true);
            PDField tagDate = form.getField("tagDate");
            tagDate.setValue("2019 / 2020");
            tagDate.setReadOnly(true);
            newDoc.addPage(doc.getPage(0));
            newDoc.addPage(doc.getPage(1));
            control = 1;
            doc = PDDocument.load(fi);
            cat = doc.getDocumentCatalog();
            form = cat.getAcroForm();
        }
    }
    PDField dateIn = form.getField("dateIn");
    dateIn.setValue("1/2019 Yearlies");
    dateIn.setReadOnly(true);
    PDField tagDate = form.getField("tagDate");
    tagDate.setValue("2019 / 2020");
    tagDate.setReadOnly(true);
    newDoc.addPage(doc.getPage(0));
    newDoc.addPage(doc.getPage(1));
    newDoc.save("PDFtest.pdf");
    Desktop.getDesktop().open(new File("PDFtest.pdf"));

}
Run Code Online (Sandbox Code Playgroud)

我无法弄清我做错了什么。这是我使用PDFbox的第一周,所以我希望它简单一些。

更新的错误信息

WARNING: Warning: You did not close a PDF Document
Exception in thread "main" java.io.IOException: COSStream has been closed and cannot be read. Perhaps its enclosing PDDocument has been closed?
    at org.apache.pdfbox.cos.COSStream.checkClosed(COSStream.java:77)
    at org.apache.pdfbox.cos.COSStream.createRawInputStream(COSStream.java:125)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromStream(COSWriter.java:1200)
    at org.apache.pdfbox.cos.COSStream.accept(COSStream.java:383)
    at org.apache.pdfbox.cos.COSObject.accept(COSObject.java:158)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObject(COSWriter.java:522)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObjects(COSWriter.java:460)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteBody(COSWriter.java:444)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromDocument(COSWriter.java:1096)
    at org.apache.pdfbox.cos.COSDocument.accept(COSDocument.java:419)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1367)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1254)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1232)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1204)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1192)
    at test.test.main(test.java:87)
Run Code Online (Sandbox Code Playgroud)

mkl*_*mkl 6

警告本身

您似乎听错了警告。它说:

警告:您没有关闭PDF文档

因此,在对比你的想法,“他说PDFBOX关闭PDDocument当其不”,PDFBox的说,你做关闭的文件!

编辑后,您会看到它实际上说a COSStream已经关闭,并且可能的原因是封闭的外壳PDDocument已经关闭。这仅仅是可能性!

您的警告

话虽如此,通过将一个文档中的页面添加到另一个文档中,您可能最终会引用两个文档中的这些页面。在那种情况下,在关闭两个文档的过程中(例如,自动通过垃圾回收自动关闭),第二次关闭实际上可能会遇到一些已经关闭的COSStream实例。

因此,我的第一个建议是最后简单地关闭文档

doc.close();
newDoc.close();
Run Code Online (Sandbox Code Playgroud)

可能不会删除警告,而只是更改其时间。

实际上,您不仅创建了两个文档,doc而且newDoc还创建了新PDDocument实例并将它们doc一次又一次地分配给该实例,在此过程中,该变量中的前一个文档对象可以自由设置为垃圾回收。因此,一旦不再引用,您最终将关闭一大堆文档。

我认为doc尽早关闭所有这些文档(尤其是在保存之前)不是一个好主意newDoc

但是,如果您的代码最终将作为大型应用程序的一部分而不是小型的一次性测试应用程序运行,则应收集所有这些PDDocument实例,Collection并在保存后立即将其明确关闭newDoc,然后清除集合。

实际上,您的异常看起来像是那些丢失的PDDocument实例之一已经被垃圾回收所关闭,因此,即使使用简单的一键式实用程序也应收集文档,以防止它们被GC处理。

(@Tilman,如果我错了,请纠正我...)

导入页面

为了防止不同文档共享页面出现问题,您可以尝试将页面导入到目标文档中,然后将导入的页面添加到目标文档页面树中。即更换

newDoc.addPage(doc.getPage(0));
newDoc.addPage(doc.getPage(1));
Run Code Online (Sandbox Code Playgroud)

通过

newDoc.addPage(newDoc.importPage(doc.getPage(0)));
newDoc.addPage(newDoc.importPage(doc.getPage(1)));
Run Code Online (Sandbox Code Playgroud)

这样可以使您在丢失每个PDDocument实例doc之前将其关闭。但是,这有一些缺点。JavaDoc方法和此处的答案

您代码中的实际问题

在合并的文档中,您将具有许多最初设置为不同值的名称相同的字段(至少在CSV文件中条目数量足够多的情况下)。并且您可以从PDAcroForm相应原始文档的中访问字段,但不要将其添加到PDAcroForm合并结果文档的中。

这是自找麻烦!PDF格式确实认为表单是文档范围内的,所有字段都直接或间接地从文档的AcroForm字典中引用,并且它期望具有相同名称的字段实际上是同一字段的不同可视化,因此所有人相同的值。

因此,PDF处理器可能会以意想不到的方式处理您的文档字段,例如

  • 通过在所有具有相同名称的字段中显示相同的值(因为它们应该具有相同的值)或
  • 通过忽略您的字段(因为它们不在文档AcroForm结构中)。

特别是,以编程方式读取PDF字段值将失败,因为在这种情况下,该表单被确定地视为文档范围的,并且基于AcroForm。另一方面,PDF查看器可能会首先显示您的设置值并使外观正常。

为避免这种情况,您应在合并之前重命名字段。您可能会考虑使用PDFMergerUtilitywhich在幕后进行这样的重命名。有关该实用工具类的用法示例,请参阅PDFMergerExample