From 22e62ce60af013a41db0f382d8ed6b031e358a2c Mon Sep 17 00:00:00 2001 From: Anton Oellerer <13524304+AntonOellerer@users.noreply.github.com> Date: Tue, 28 May 2024 14:29:19 +0200 Subject: [PATCH] Extend `CustomWordPlaceholderData` to insert into header/footer Up until now, the processing flow when inserting a paragraph into a header or footer lead to a `ClassCastException`, as jocument assumed that the insertion happened into a `XWPFDocument`. To fix this, the abstract method for transforming a placeholder now expects an `IBody` element instead of a `XWPFDocument`, where `IBody` is an interface which describes the possible insertion methods. This allows jocument to insert paragraphs into any kind of xwpf part. --- build.gradle | 2 +- .../impl/word/CustomWordPlaceholderData.java | 18 ++++++++++-------- .../placeholders/ImagePlaceholderData.java | 6 +++--- .../jocument/impl/word/WordGeneratorTest.java | 16 ++++++++++++++++ .../localisation/CustomTranslationsTests.java | 3 ++- .../sample/placeholders/QuotePlaceholder.java | 6 +++--- .../templates/word/PictureInHeader.docx | Bin 0 -> 8819 bytes 7 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 src/test/resources/templates/word/PictureInHeader.docx diff --git a/build.gradle b/build.gradle index 49e3ea88..ff2ebfa2 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group 'com.docutools' -version = '1.8.0' +version = '2.0.0' java { toolchain { diff --git a/src/main/java/com/docutools/jocument/impl/word/CustomWordPlaceholderData.java b/src/main/java/com/docutools/jocument/impl/word/CustomWordPlaceholderData.java index bc577b21..9bac43dc 100644 --- a/src/main/java/com/docutools/jocument/impl/word/CustomWordPlaceholderData.java +++ b/src/main/java/com/docutools/jocument/impl/word/CustomWordPlaceholderData.java @@ -6,8 +6,8 @@ import java.util.Locale; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.poi.xwpf.usermodel.IBody; import org.apache.poi.xwpf.usermodel.IBodyElement; -import org.apache.poi.xwpf.usermodel.XWPFDocument; public abstract class CustomWordPlaceholderData implements PlaceholderData { private static final Logger logger = LogManager.getLogger(); @@ -19,16 +19,18 @@ public PlaceholderType getType() { @Override public void transform(Object placeholder, Locale locale, GenerationOptions options) { - if (!(placeholder instanceof IBodyElement element)) { + if (placeholder instanceof IBodyElement element) { + if (element.getPart() instanceof IBody bodyPart) { + transform(element, bodyPart, locale, options); + } else { + logger.error("Parent of {} is not an instance of IBody", placeholder); + throw new IllegalArgumentException("Only children of IBody objects accepted."); + } + } else { logger.error("{} is not an instance of IBodyElement", placeholder); throw new IllegalArgumentException("Only IBodyElements accepted."); } - - var document = element.getBody().getXWPFDocument(); - - transform(element, document, locale, options); } - protected abstract void transform(IBodyElement placeholder, XWPFDocument document, Locale locale, GenerationOptions options); - + protected abstract void transform(IBodyElement placeholder, IBody part, Locale locale, GenerationOptions options); } diff --git a/src/main/java/com/docutools/jocument/impl/word/placeholders/ImagePlaceholderData.java b/src/main/java/com/docutools/jocument/impl/word/placeholders/ImagePlaceholderData.java index f0b7689b..5bf23fe5 100644 --- a/src/main/java/com/docutools/jocument/impl/word/placeholders/ImagePlaceholderData.java +++ b/src/main/java/com/docutools/jocument/impl/word/placeholders/ImagePlaceholderData.java @@ -10,8 +10,8 @@ import java.util.Locale; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.poi.xwpf.usermodel.IBody; import org.apache.poi.xwpf.usermodel.IBodyElement; -import org.apache.poi.xwpf.usermodel.XWPFDocument; public class ImagePlaceholderData extends CustomWordPlaceholderData { private static final Logger logger = LogManager.getLogger(); @@ -31,10 +31,10 @@ public ImagePlaceholderData withMaxWidth(int maxWidth) { } @Override - protected void transform(IBodyElement placeholder, XWPFDocument document, Locale locale, GenerationOptions options) { + protected void transform(IBodyElement placeholder, IBody part, Locale locale, GenerationOptions options) { Path path = applyOptions(options); try { - var paragraph = document.insertNewParagraph(WordUtilities.openCursor(placeholder).orElseThrow()); + var paragraph = part.insertNewParagraph(WordUtilities.openCursor(placeholder).orElseThrow()); WordImageUtils.insertImage(paragraph, path, options.imageStrategy()); WordUtilities.removeIfExists(placeholder); } finally { 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 c98e82a0..cfcc9196 100644 --- a/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java +++ b/src/test/java/com/docutools/jocument/impl/word/WordGeneratorTest.java @@ -512,4 +512,20 @@ void dynamicAccess() throws InterruptedException, IOException { assertThat(documentWrapper.bodyElement(0).asParagraph().run(0).text(), equalTo(SampleModelData.ENTERPRISE.name())); } + @Test + void pictureInHeader() throws InterruptedException, IOException { + // Arrange + Template template = Template.fromClassPath("/templates/word/PictureInHeader.docx") + .orElseThrow(); + PlaceholderResolver resolver = new ReflectionResolver(SampleModelData.PICARD); + + // Act + Document document = template.startGeneration(resolver); + document.blockUntilCompletion(60000L); // 1 minute + + // Assert + assertThat(document.completed(), is(true)); + xwpfDocument = TestUtils.getXWPFDocumentFromDocument(document); + assertThat(xwpfDocument.getHeaderArray(0).getAllPictures(), hasSize(1)); + } } \ No newline at end of file diff --git a/src/test/java/com/docutools/jocument/localisation/CustomTranslationsTests.java b/src/test/java/com/docutools/jocument/localisation/CustomTranslationsTests.java index 2c5d03ba..d5fffdcf 100644 --- a/src/test/java/com/docutools/jocument/localisation/CustomTranslationsTests.java +++ b/src/test/java/com/docutools/jocument/localisation/CustomTranslationsTests.java @@ -15,6 +15,7 @@ import com.docutools.jocument.sample.model.SampleModelData; import java.util.Locale; import java.util.Optional; +import org.apache.poi.xwpf.usermodel.IBody; import org.apache.poi.xwpf.usermodel.IBodyElement; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; @@ -103,7 +104,7 @@ public CustomTranslatedPlaceholder() { } @Override - protected void transform(IBodyElement placeholder, XWPFDocument document, Locale locale, GenerationOptions options) { + protected void transform(IBodyElement placeholder, IBody part, Locale locale, GenerationOptions options) { if (placeholder instanceof XWPFParagraph paragraph) { var translatedText = options.translate(paragraph.getText(), locale).orElse(""); WordUtilities.replaceText(paragraph, translatedText); diff --git a/src/test/java/com/docutools/jocument/sample/placeholders/QuotePlaceholder.java b/src/test/java/com/docutools/jocument/sample/placeholders/QuotePlaceholder.java index 713bd946..2f24aefa 100644 --- a/src/test/java/com/docutools/jocument/sample/placeholders/QuotePlaceholder.java +++ b/src/test/java/com/docutools/jocument/sample/placeholders/QuotePlaceholder.java @@ -4,13 +4,13 @@ import com.docutools.jocument.impl.word.CustomWordPlaceholderData; import com.docutools.jocument.impl.word.WordUtilities; import java.util.Locale; +import org.apache.poi.xwpf.usermodel.IBody; import org.apache.poi.xwpf.usermodel.IBodyElement; -import org.apache.poi.xwpf.usermodel.XWPFDocument; public class QuotePlaceholder extends CustomWordPlaceholderData { @Override - protected void transform(IBodyElement placeholder, XWPFDocument document, Locale locale, GenerationOptions options) { - var paragraph = document.insertNewParagraph(WordUtilities.openCursor(placeholder).orElseThrow()); + protected void transform(IBodyElement placeholder, IBody part, Locale locale, GenerationOptions options) { + var paragraph = part.insertNewParagraph(WordUtilities.openCursor(placeholder).orElseThrow()); paragraph.createRun().setText("Live your life not celebrating victories, but overcoming defeats."); WordUtilities.removeIfExists(placeholder); } diff --git a/src/test/resources/templates/word/PictureInHeader.docx b/src/test/resources/templates/word/PictureInHeader.docx new file mode 100644 index 0000000000000000000000000000000000000000..d32e0a9b91e66ae6cbfd665257562257b7a93519 GIT binary patch literal 8819 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 literal 0 HcmV?d00001