PDFbox 说 PDDocument 关闭时它不是
PDFbox saying PDDocument closed when its not
我正在尝试使用 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"));
}
我一辈子都弄不明白自己做错了什么。这是我使用 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)
警告本身
您似乎听错了警告。它说:
Warning: You did not close a PDF Document
因此,与您的想法相反,“PDFbox 说 PDDocument 未关闭”,PDFBox 说您 未 关闭一个文档!
在你编辑之后,你会看到它实际上说 COSStream
已经关闭并且 可能 原因 是封闭的PDDocument
已经被关闭。这只是一种可能性!
您案例中的警告
也就是说,通过将一个文档的页面添加到另一个文档,您可能最终会从两个文档中引用这些页面。在那种情况下,在关闭两个文档的过程中(例如通过垃圾收集自动关闭),第二次关闭可能确实会遇到一些已经关闭的 COSStream
个实例。
所以我的第一个建议是 关闭 最后的文档
doc.close();
newDoc.close();
可能不会删除警告,只是更改它们的时间。
实际上,您不仅创建了两个文档 doc
和 newDoc
,您甚至还创建了新的 PDDocument
实例并将它们一次又一次地分配给 doc
,在该过程将该变量中的前文档对象设置为免费用于垃圾收集。所以你最终会有一大堆文档一旦不再被引用就需要关闭。
我认为尽早关闭 doc
中的所有这些文档不是一个好主意,尤其是在保存 newDoc
.
之前
但是,如果您的代码最终将 运行 作为大型应用程序的一部分,而不是作为小型的一次性测试应用程序,您应该在某些 Collection
并在保存后立即明确关闭它们 newDoc
然后清除集合。
实际上你的异常看起来像那些丢失的 PDDocument
实例之一已经被垃圾收集关闭,所以你应该收集文档,即使是在一个简单的一次性实用程序的情况下,以防止它们被 GC处置。
(@Tilman,如有错误请指正...)
正在导入页面
为防止不同文档共享页面出现问题,您可以尝试 导入 页面到目标文档,然后添加导入的页面到目标文档页面树。 IE。替换
newDoc.addPage(doc.getPage(0));
newDoc.addPage(doc.getPage(1));
来自
newDoc.addPage(newDoc.importPage(doc.getPage(0)));
newDoc.addPage(newDoc.importPage(doc.getPage(1)));
这应该允许您在丢失前关闭 doc
中的每个 PDDocument
实例。
但是,这有一些缺点,请参见。方法 JavaDoc 和 .
您的代码中存在实际问题
在您的合并文档中,您将有许多具有相同名称的字段(至少在您的 CSV 文件中条目数量足够多的情况下),这些字段最初设置为不同的值。并且您从相应原始文档的 PDAcroForm
访问字段,但不要将它们添加到组合结果文档的 PDAcroForm
。
这是自找麻烦! PDF 格式确实将表单视为文档范围内的所有字段(直接或间接)从文档的 AcroForm 字典中引用,并且它期望具有相同名称的字段实际上是同一字段的不同可视化,因此所有字段都具有相同的值。
因此,PDF 处理器可能会以意想不到的方式处理您的文档字段,例如
- 通过在具有相同名称的所有字段中显示相同的值(因为它们应该具有相同的值)或
- 忽略您的字段(因为它们不在文档 AcroForm 结构中)。
特别是您的 PDF 字段值的编程读取将失败,因为在这种情况下,表单被明确地认为是文档范围的并且基于 AcroForm。另一方面,PDF 查看器可能会先显示您的设置值,然后看起来一切正常。
为防止这种情况,您应该在合并前重命名字段。您可能会考虑使用 PDFMergerUtility
来在后台进行这样的重命名。有关该实用程序 class 的示例用法,请查看 PDFMergerExample
.
尽管上面的答案被标记为问题的解决方案,但由于解决方案被埋在评论中,我想在这个级别添加这个答案。我花了几个小时寻找解决方案。
我的代码片段和评论。
// Collection solely for purpose of preventing premature garbage collection
List<PDDocument> sourceDocuments = new ArrayList<>( );
...
// Source document (actually inside a loop)
PDDocument docIn = PDDocument.load( artifactBytes );
// Add document to collection before using it to prevent the problem
sourceDocuments.add( docIn );
// Extract from source document
PDPage extractedPage = docIn.getPage( 0 );
// Add page to destination document
docOut.addPage( extractedPage );
...
// This was failing with "COSStream has been closed and cannot be read."
// Now it works.
docOut.save( bundleStream );
我正在尝试使用 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"));
}
我一辈子都弄不明白自己做错了什么。这是我使用 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)
警告本身
您似乎听错了警告。它说:
Warning: You did not close a PDF Document
因此,与您的想法相反,“PDFbox 说 PDDocument 未关闭”,PDFBox 说您 未 关闭一个文档!
在你编辑之后,你会看到它实际上说 COSStream
已经关闭并且 可能 原因 是封闭的PDDocument
已经被关闭。这只是一种可能性!
您案例中的警告
也就是说,通过将一个文档的页面添加到另一个文档,您可能最终会从两个文档中引用这些页面。在那种情况下,在关闭两个文档的过程中(例如通过垃圾收集自动关闭),第二次关闭可能确实会遇到一些已经关闭的 COSStream
个实例。
所以我的第一个建议是 关闭 最后的文档
doc.close();
newDoc.close();
可能不会删除警告,只是更改它们的时间。
实际上,您不仅创建了两个文档 doc
和 newDoc
,您甚至还创建了新的 PDDocument
实例并将它们一次又一次地分配给 doc
,在该过程将该变量中的前文档对象设置为免费用于垃圾收集。所以你最终会有一大堆文档一旦不再被引用就需要关闭。
我认为尽早关闭 doc
中的所有这些文档不是一个好主意,尤其是在保存 newDoc
.
但是,如果您的代码最终将 运行 作为大型应用程序的一部分,而不是作为小型的一次性测试应用程序,您应该在某些 Collection
并在保存后立即明确关闭它们 newDoc
然后清除集合。
实际上你的异常看起来像那些丢失的 PDDocument
实例之一已经被垃圾收集关闭,所以你应该收集文档,即使是在一个简单的一次性实用程序的情况下,以防止它们被 GC处置。
(@Tilman,如有错误请指正...)
正在导入页面
为防止不同文档共享页面出现问题,您可以尝试 导入 页面到目标文档,然后添加导入的页面到目标文档页面树。 IE。替换
newDoc.addPage(doc.getPage(0));
newDoc.addPage(doc.getPage(1));
来自
newDoc.addPage(newDoc.importPage(doc.getPage(0)));
newDoc.addPage(newDoc.importPage(doc.getPage(1)));
这应该允许您在丢失前关闭 doc
中的每个 PDDocument
实例。
但是,这有一些缺点,请参见。方法 JavaDoc 和
您的代码中存在实际问题
在您的合并文档中,您将有许多具有相同名称的字段(至少在您的 CSV 文件中条目数量足够多的情况下),这些字段最初设置为不同的值。并且您从相应原始文档的 PDAcroForm
访问字段,但不要将它们添加到组合结果文档的 PDAcroForm
。
这是自找麻烦! PDF 格式确实将表单视为文档范围内的所有字段(直接或间接)从文档的 AcroForm 字典中引用,并且它期望具有相同名称的字段实际上是同一字段的不同可视化,因此所有字段都具有相同的值。
因此,PDF 处理器可能会以意想不到的方式处理您的文档字段,例如
- 通过在具有相同名称的所有字段中显示相同的值(因为它们应该具有相同的值)或
- 忽略您的字段(因为它们不在文档 AcroForm 结构中)。
特别是您的 PDF 字段值的编程读取将失败,因为在这种情况下,表单被明确地认为是文档范围的并且基于 AcroForm。另一方面,PDF 查看器可能会先显示您的设置值,然后看起来一切正常。
为防止这种情况,您应该在合并前重命名字段。您可能会考虑使用 PDFMergerUtility
来在后台进行这样的重命名。有关该实用程序 class 的示例用法,请查看 PDFMergerExample
.
尽管上面的答案被标记为问题的解决方案,但由于解决方案被埋在评论中,我想在这个级别添加这个答案。我花了几个小时寻找解决方案。
我的代码片段和评论。
// Collection solely for purpose of preventing premature garbage collection
List<PDDocument> sourceDocuments = new ArrayList<>( );
...
// Source document (actually inside a loop)
PDDocument docIn = PDDocument.load( artifactBytes );
// Add document to collection before using it to prevent the problem
sourceDocuments.add( docIn );
// Extract from source document
PDPage extractedPage = docIn.getPage( 0 );
// Add page to destination document
docOut.addPage( extractedPage );
...
// This was failing with "COSStream has been closed and cannot be read."
// Now it works.
docOut.save( bundleStream );