From 8eec3e81636a654de22ff4ff2a739b692582c619 Mon Sep 17 00:00:00 2001 From: Anton Oellerer <13524304+AntonOellerer@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:02:18 +0100 Subject: [PATCH] Remove last word page if it is empty --- build.gradle | 2 +- .../jocument/impl/word/WordDocumentImpl.java | 32 ++++++++++++++++++ .../jocument/impl/word/WordGeneratorTest.java | 17 ++++++++++ .../templates/word/EmptyTemplate.docx | Bin 0 -> 8865 bytes 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/templates/word/EmptyTemplate.docx 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 0000000000000000000000000000000000000000..c0b404fc03992b1d59d6c920927e93fbc627e950 GIT binary patch literal 8865 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 literal 0 HcmV?d00001