diff --git a/build.gradle b/build.gradle index d3b7d8e..76ae7a6 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group 'com.docutools' -version = '8.1.0' +version = '8.2.0' java { toolchain { diff --git a/src/main/java/com/docutools/jocument/impl/word/WordDocumentImpl.java b/src/main/java/com/docutools/jocument/impl/word/WordDocumentImpl.java index 3693b9a..fe82aae 100644 --- a/src/main/java/com/docutools/jocument/impl/word/WordDocumentImpl.java +++ b/src/main/java/com/docutools/jocument/impl/word/WordDocumentImpl.java @@ -14,6 +14,8 @@ import org.apache.logging.log4j.Logger; import org.apache.poi.xwpf.usermodel.IBodyElement; import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; public class WordDocumentImpl extends DocumentImpl { private static final Logger logger = LogManager.getLogger(); @@ -35,6 +37,8 @@ protected Path generate() throws IOException { logger.debug("Retrieved all body elements, starting WordGenerator"); WordGenerator.apply(resolver, bodyElements, options); + cleanLastEmptyPage(document); + document.enforceUpdateFields(); try (OutputStream os = Files.newOutputStream(file)) { @@ -46,4 +50,32 @@ protected Path generate() throws IOException { return file; } + private void cleanLastEmptyPage(XWPFDocument document) { + List elements = document.getBodyElements(); + int elementsToRemove = 0; + for (int i = elements.size() - 1; i >= 0; i--) { + IBodyElement element = elements.get(i); + if (element instanceof XWPFParagraph xwpfParagraph && isEmpty(xwpfParagraph)) { + elementsToRemove++; + } else { + break; + } + } + for (; elementsToRemove > 0; elementsToRemove--) { + document.removeBodyElement(elements.size() - 1); + } + } + + private boolean isEmpty(XWPFParagraph xwpfParagraph) { + return xwpfParagraph.isPageBreak() || (xwpfParagraph.getText().trim().isEmpty() && !hasPictures(xwpfParagraph)); + } + + private boolean hasPictures(XWPFParagraph paragraph) { + for (XWPFRun xwpfRun : paragraph.getRuns()) { + if (!xwpfRun.getEmbeddedPictures().isEmpty()) { + return true; + } + } + return false; + } } diff --git a/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java b/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java index 0cbd0da..d736b3c 100644 --- a/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java +++ b/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java @@ -607,4 +607,21 @@ void preservesLinebreaksInStrings() throws InterruptedException, IOException { assertThat(xwpfRun.getText(1), equalTo(" Socci Mignon ")); assertThat(xwpfRun.getText(2), equalTo(" Ellworths")); } + + @Test + void deletesEmptyPage() throws InterruptedException, IOException { + // Arrange + Template template = Template.fromClassPath("/templates/word/EmptyTemplate.docx") + .orElseThrow(); + PlaceholderResolver resolver = new ReflectionResolver(SampleModelData.LINEBREAK_NAME_PERSON); + + // Act + Document document = template.startGeneration(resolver); + document.blockUntilCompletion(60000L); // 1 minute + + // Assert + assertThat(document.completed(), is(true)); + xwpfDocument = TestUtils.getXWPFDocumentFromDocument(document); + assertThat(xwpfDocument.getBodyElements().size(), equalTo(0)); + } } \ No newline at end of file diff --git a/src/test/resources/templates/word/EmptyTemplate.docx b/src/test/resources/templates/word/EmptyTemplate.docx new file mode 100644 index 0000000..c0b404f Binary files /dev/null and b/src/test/resources/templates/word/EmptyTemplate.docx differ