From 18470eaad7d6c487a514db3ea57696d3a9e8e0f0 Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Mon, 4 Nov 2024 22:58:40 +0800 Subject: [PATCH 1/7] docs(readme): clarify usage and enhance code snippets for addCustomFunction feature. Improve documentation by refining explanations, examples and details on custom functions in readme. This ensures users understand the extended capabilities of OfficeStamper. --- README.adoc | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/README.adoc b/README.adoc index c58dd142..74863142 100644 --- a/README.adoc +++ b/README.adoc @@ -8,7 +8,7 @@ == Introduction -OfficeStamper (formerly Docx-Stamper) is a Java template engine that allows for dynamic creation of docx documents at runtime. +OfficeStamper (formerly Docx-Stamper) is a Java template engine that allows for dynamic creation of DOCX documents at runtime. You design a template using your preferred Word processor; and OfficeStamper will generate documents based on that template. image:{proj}/actions/workflows/integrate-os.yml/badge.svg[Build Status,link={proj}/actions/workflows/integrate-os.yml] image:{proj}/actions/workflows/integrate-docx4j.yml/badge.svg[Build Status,link={proj}/actions/workflows/integrate-docx4j.yml] image:{proj}/actions/workflows/analyze.yml/badge.svg[Build Status,link={proj}/actions/workflows/analyze.yml] image:{proj}/actions/workflows/pages.yml/badge.svg[Build Status,link={proj}/actions/workflows/pages.yml] @@ -54,7 +54,6 @@ image:{proj}/actions/workflows/integrate-os.yml/badge.svg[Build Status,link={pro * *BREAK* Removed DocxDocument.commentsPart method. * *BREAK* DocxPart.streamParagraphs method now returns the Paragraph wrapper, instead of docx4j P. - ==== Tests * Improved test names, @@ -66,20 +65,20 @@ image:{proj}/actions/workflows/integrate-os.yml/badge.svg[Build Status,link={pro == Usage -Here is a simple code snippet exemplifying how to use OfficeStamper: +Here is a code snippet exemplifying how to use OfficeStamper: [source,java] ---- class Example { public static void main(String[] args) { - // your own POJO against which expressions found in the template will be resolved + // a java object to use as context for the expressions found in the template. var context = new YourPojoContext(_, _ , _); // an instance of the stamper var stamper = OfficeStampers.docxStamper(); try( - // Path to your .docx template file + // Path to the .docx template file var template = Files.newInputStream(Paths.get("your/docx/template/file.docx")); // Path to write the resulting .docx document var output = Files.newOutputStream(Paths.get("your/desired/output/path.docx")) @@ -173,7 +172,7 @@ Office-stamper provides some function already added to the standard configuratio === Custom resolvers -You can expand the resolution functionality by implementing custom `link:{engine}api/ObjectResolver.java[ObjectResolver]`. +You can expand the resolution capability by implementing custom `link:{engine}api/ObjectResolver.java[ObjectResolver]`. Here's a code snippet on how to proceed: @@ -198,7 +197,7 @@ class Main { === Custom functions -OfficeStamper lets you add custom functions to the tool’s expression language. +OfficeStamper lets you add custom functions to the tool's expression language. For example, if you need specific formats for numbers or dates, you can register such functions which can then be used in the placeholders throughout your template. Below is a sample code demonstrating how to extend the expression language with a custom function. @@ -206,20 +205,40 @@ This particular example adds a function `toUppercase(String)`, enabling you to c [source,java] ---- -class Main { +import java.time.LocalDate;import java.time.format.DateTimeFormatter;class Main { public static void main(String... args) { - interface UppercaseFunction { + var configuration = OfficeStamperConfigurations.standardWithPreprocessing(); + + // add `today()` function to use in the template to retrieve current date, at time of running the stamping + config.addCustomFunction("today", () -> LocalDate.now()); + + // add `censor(String)` function, to remove the f-word from resolved template values. + config.addCustomFunction("censor", String.class, input -> input.replace("f-word", "f**k")); + + // add `add(Integer, Integer)` function to sum 2 values together after their resolution. + config.addCustomFunction("add", Integer.class, Integer.class, (a, b) -> a + b); + + // add `format(Date, String, String)` function to format a date with a pattern and a locale. + config.addCustomFunction("format", LocalDate.class, String.class, String.class, (date, pattern, locale) -> DateTimeFormatter.ofPattern(pattern, locale).format(date)); + + // + interface StringFunctionProvider { String toUppercase(String string); + String toLowercase(String string); } - var configuration = OfficeStamperConfigurations.standardWithPreprocessing(); - configuration.exposeInterfaceToExpressionLanguage(UppercaseFunction.class, String::toUppercase); + class StringFunctionProviderImpl implements StringFunctionProvider { + String toUppercase(String string){return string.toUpperCase();} + String toLowercase(String string){return string.toUpperCase();} + } + + configuration.exposeInterfaceToExpressionLanguage(UppercaseFunction.class, new StringFunctionProviderImpl()); var stamper = OfficeStampers.docxStamper(configuration); } } ---- -Chains of such custom functions can enhance the versatility of OfficeStamper, making it capable of handling complex and unique templating situations. +Chains of such custom functions can enhance the versatility of OfficeStamper, making it able to handle complex and unique templating situations. === Custom Comment Processors From 3e2b893ffc351f8775f9d245256f8020f31394f5 Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Mon, 4 Nov 2024 23:00:09 +0800 Subject: [PATCH 2/7] refactor: simplify comment collection process Consolidate comment collection logic into CommentProcessorRegistry, removing redundant CommentCollectorWalker class. Simplifies code maintenance and enhances readability by using utilities under WmlUtils. --- .../core/BaseDocumentWalker.java | 29 +----- .../core/CommentCollectorWalker.java | 95 ------------------- .../core/CommentProcessorRegistry.java | 68 +++++++++++-- .../officestamper/core/DocumentWalker.java | 32 ------- .../RemoveMalformedComments.java | 42 ++++---- .../verron/officestamper/utils/WmlUtils.java | 10 ++ 6 files changed, 93 insertions(+), 183 deletions(-) delete mode 100644 engine/src/main/java/pro/verron/officestamper/core/CommentCollectorWalker.java diff --git a/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java b/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java index cf0eeacf..73457fe1 100644 --- a/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java +++ b/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java @@ -1,6 +1,9 @@ package pro.verron.officestamper.core; -import org.docx4j.wml.*; +import org.docx4j.wml.CommentRangeEnd; +import org.docx4j.wml.CommentRangeStart; +import org.docx4j.wml.P; +import org.docx4j.wml.R; import pro.verron.officestamper.api.DocxPart; /** @@ -32,30 +35,6 @@ protected void onParagraph(P paragraph) { } - /** {@inheritDoc} */ - @Override - protected void onRun(R run) { - - } - - /** {@inheritDoc} */ - @Override - protected void onTable(Tbl table) { - - } - - /** {@inheritDoc} */ - @Override - protected void onTableCell(Tc tableCell) { - - } - - /** {@inheritDoc} */ - @Override - protected void onTableRow(Tr tableRow) { - - } - /** {@inheritDoc} */ @Override protected void onCommentRangeStart(CommentRangeStart commentRangeStart) { diff --git a/engine/src/main/java/pro/verron/officestamper/core/CommentCollectorWalker.java b/engine/src/main/java/pro/verron/officestamper/core/CommentCollectorWalker.java deleted file mode 100644 index 15ca0074..00000000 --- a/engine/src/main/java/pro/verron/officestamper/core/CommentCollectorWalker.java +++ /dev/null @@ -1,95 +0,0 @@ -package pro.verron.officestamper.core; - -import org.docx4j.wml.CommentRangeEnd; -import org.docx4j.wml.CommentRangeStart; -import org.docx4j.wml.Comments; -import org.docx4j.wml.R; -import pro.verron.officestamper.api.Comment; -import pro.verron.officestamper.api.DocxPart; -import pro.verron.officestamper.api.OfficeStamperException; - -import java.math.BigInteger; -import java.util.*; - -class CommentCollectorWalker - extends BaseDocumentWalker { - private final DocxPart document; - private final Map allComments; - private final Queue stack; - private final Map rootComments; - - private CommentCollectorWalker( - DocxPart document, - Map rootComments, - Map allComments - ) { - super(document); - this.document = document; - this.allComments = allComments; - this.stack = Collections.asLifoQueue(new ArrayDeque<>()); - this.rootComments = rootComments; - } - - static Map collectComments(DocxPart docxPart) { - var rootComments = new HashMap(); - var allComments = new HashMap(); - new CommentCollectorWalker(docxPart, rootComments, allComments).walk(); - - var sourceDocument = docxPart.document(); - CommentUtil.getCommentsPart(sourceDocument.getParts()) - .map(CommentUtil::extractContent) - .map(Comments::getComment) - .stream() - .flatMap(Collection::stream) - .filter(comment -> allComments.containsKey(comment.getId())) - .forEach(comment -> allComments.get(comment.getId()) - .setComment(comment)); - return new HashMap<>(rootComments); - } - - - @Override - protected void onCommentRangeStart(CommentRangeStart commentRangeStart) { - Comment comment = allComments.get(commentRangeStart.getId()); - if (comment == null) { - comment = new StandardComment(document.document()); - allComments.put(commentRangeStart.getId(), comment); - if (stack.isEmpty()) { - rootComments.put(commentRangeStart.getId(), comment); - } - else { - stack.peek() - .getChildren() - .add(comment); - } - } - comment.setCommentRangeStart(commentRangeStart); - stack.add(comment); - } - - @Override - protected void onCommentRangeEnd(CommentRangeEnd commentRangeEnd) { - Comment comment = allComments.get(commentRangeEnd.getId()); - if (comment == null) - throw new OfficeStamperException("Found a comment range end before the comment range start !"); - - comment.setCommentRangeEnd(commentRangeEnd); - - if (stack.isEmpty()) return; - - var peek = stack.peek(); - if (peek.equals(comment)) - stack.remove(); - else throw new OfficeStamperException("Cannot figure which comment contains the other !"); - } - - @Override - protected void onCommentReference(R.CommentReference commentReference) { - Comment comment = allComments.get(commentReference.getId()); - if (comment == null) { - comment = new StandardComment(document.document()); - allComments.put(commentReference.getId(), comment); - } - comment.setCommentReference(commentReference); - } -} diff --git a/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java b/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java index 33a18bf6..1f729e8c 100644 --- a/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java +++ b/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java @@ -1,20 +1,18 @@ package pro.verron.officestamper.core; -import org.docx4j.wml.P; -import org.docx4j.wml.R; +import org.docx4j.wml.*; +import org.jvnet.jaxb2_commons.ppp.Child; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelParseException; import pro.verron.officestamper.api.*; import pro.verron.officestamper.utils.WmlFactory; +import pro.verron.officestamper.utils.WmlUtils; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Map; -import java.util.Optional; +import java.util.*; -import static pro.verron.officestamper.core.CommentCollectorWalker.collectComments; import static pro.verron.officestamper.core.Placeholders.findProcessors; /** @@ -56,6 +54,64 @@ public CommentProcessorRegistry( this.exceptionResolver = exceptionResolver; } + private static Map collectComments(DocxPart document) { + var rootComments = new HashMap(); + var allComments = new HashMap(); + var stack = Collections.asLifoQueue(new ArrayDeque()); + + var list = WmlUtils.extractCommentElements(document.document()); + for (Child commentElement : list) { + if (commentElement instanceof CommentRangeStart crs) { + Comment comment = allComments.get(crs.getId()); + if (comment == null) { + comment = new StandardComment(document.document()); + allComments.put(crs.getId(), comment); + if (stack.isEmpty()) { + rootComments.put(crs.getId(), comment); + } + else { + stack.peek() + .getChildren() + .add(comment); + } + } + comment.setCommentRangeStart(crs); + stack.add(comment); + } + else if (commentElement instanceof CommentRangeEnd cre) { + Comment comment = allComments.get(cre.getId()); + if (comment == null) + throw new OfficeStamperException("Found a comment range end before the comment range start !"); + + comment.setCommentRangeEnd(cre); + + if (!stack.isEmpty()) { + var peek = stack.peek(); + if (peek.equals(comment)) stack.remove(); + else throw new OfficeStamperException("Cannot figure which comment contains the other !"); + } + } + else if (commentElement instanceof R.CommentReference cr) { + Comment comment = allComments.get(cr.getId()); + if (comment == null) { + comment = new StandardComment(document.document()); + allComments.put(cr.getId(), comment); + } + comment.setCommentReference(cr); + } + } + var sourceDocument = document.document(); + CommentUtil.getCommentsPart(sourceDocument.getParts()) + .map(CommentUtil::extractContent) + .map(Comments::getComment) + .stream() + .flatMap(Collection::stream) + .filter(comment -> allComments.containsKey(comment.getId())) + .forEach(comment -> allComments.get(comment.getId()) + .setComment(comment)); + return new HashMap<>(rootComments); + } + public void runProcessors(T expressionContext) { var proceedComments = new ArrayList(); diff --git a/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java b/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java index c7743d30..52a9a38c 100644 --- a/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java +++ b/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java @@ -64,7 +64,6 @@ public void walk() { } private void walkTable(Tbl table) { - onTable(table); for (Object contentElement : table.getContent()) { Object unwrappedObject = XmlUtils.unwrap(contentElement); if (unwrappedObject instanceof Tr row) { @@ -74,7 +73,6 @@ private void walkTable(Tbl table) { } private void walkTableRow(Tr row) { - onTableRow(row); for (Object rowContentElement : row.getContent()) { Object unwrappedObject = XmlUtils.unwrap(rowContentElement); if (unwrappedObject instanceof Tc cell) { @@ -84,7 +82,6 @@ private void walkTableRow(Tr row) { } private void walkTableCell(Tc cell) { - onTableCell(cell); for (Object cellContentElement : cell.getContent()) { Object unwrappedObject = XmlUtils.unwrap(cellContentElement); if (unwrappedObject instanceof P) { @@ -124,7 +121,6 @@ else if (unwrappedObject instanceof CommentRangeEnd commentRangeEnd) { } private void walkRun(R r) { - onRun(r); for (Object element : r.getContent()) { Object unwrappedObject = XmlUtils.unwrap(element); if (unwrappedObject instanceof CommentReference commentReference) { @@ -133,13 +129,6 @@ private void walkRun(R r) { } } - /** - * This method is called for every {@link R} element in the document. - * - * @param run the {@link R} element to process. - */ - protected abstract void onRun(R run); - /** * This method is called for every {@link P} element in the document. * @@ -147,27 +136,6 @@ private void walkRun(R r) { */ protected abstract void onParagraph(P paragraph); - /** - * This method is called for every {@link Tbl} element in the document. - * - * @param table the {@link Tbl} element to process. - */ - protected abstract void onTable(Tbl table); - - /** - * This method is called for every {@link Tc} element in the document. - * - * @param tableCell the {@link Tc} element to process. - */ - protected abstract void onTableCell(Tc tableCell); - - /** - * This method is called for every {@link Tr} element in the document. - * - * @param tableRow the {@link Tr} element to process. - */ - protected abstract void onTableRow(Tr tableRow); - /** * This method is called for every {@link CommentRangeStart} element in the document. * diff --git a/engine/src/main/java/pro/verron/officestamper/preset/preprocessors/malformedcomments/RemoveMalformedComments.java b/engine/src/main/java/pro/verron/officestamper/preset/preprocessors/malformedcomments/RemoveMalformedComments.java index 499e53cd..9e75a32d 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/preprocessors/malformedcomments/RemoveMalformedComments.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/preprocessors/malformedcomments/RemoveMalformedComments.java @@ -1,7 +1,6 @@ package pro.verron.officestamper.preset.preprocessors.malformedcomments; import org.docx4j.TraversalUtil; -import org.docx4j.finders.CommentFinder; import org.docx4j.openpackaging.exceptions.Docx4JException; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.WordprocessingML.CommentsPart; @@ -11,6 +10,7 @@ import org.slf4j.LoggerFactory; import pro.verron.officestamper.api.OfficeStamperException; import pro.verron.officestamper.api.PreProcessor; +import pro.verron.officestamper.utils.WmlUtils; import java.math.BigInteger; import java.util.*; @@ -22,7 +22,7 @@ public class RemoveMalformedComments private static final Logger log = LoggerFactory.getLogger(RemoveMalformedComments.class); @Override public void process(WordprocessingMLPackage document) { - var commentElements = getCommentElements(document); + var commentElements = WmlUtils.extractCommentElements(document); var commentIds = new ArrayList(commentElements.size()); var openedCommentsIds = new ArrayDeque(); @@ -59,41 +59,28 @@ else if (commentElement instanceof R.CommentReference cr) { log.debug("These comments have been opened, but never closed: {}", openedCommentsIds); var malformedCommentIds = new ArrayList<>(openedCommentsIds); - Set writtenCommentsId = Optional.ofNullable(document.getMainDocumentPart() - .getCommentsPart()) + var mainDocumentPart = document.getMainDocumentPart(); + Set writtenCommentsId = Optional.ofNullable(mainDocumentPart.getCommentsPart()) .map(RemoveMalformedComments::tryGetCommentsPart) .map(Comments::getComment) .orElse(Collections.emptyList()) .stream() - .filter(c -> c.getContent() != null) - .filter(c -> !c.getContent() - .isEmpty()) + .filter(c -> !isEmpty(c)) .map(CTMarkup::getId) .collect(toSet()); - commentIds.removeAll(writtenCommentsId); log.debug("These comments have been referenced in body, but have no related content: {}", commentIds); malformedCommentIds.addAll(commentIds); - var commentReferenceRemoverVisitor = new CommentReferenceRemoverVisitor(malformedCommentIds); - var commentRangeStartRemoverVisitor = new CommentRangeStartRemoverVisitor(malformedCommentIds); - var commentRangeEndRemoverVisitor = new CommentRangeEndRemoverVisitor(malformedCommentIds); - TraversalUtil.visit(document, - true, - List.of(commentReferenceRemoverVisitor, - commentRangeStartRemoverVisitor, - commentRangeEndRemoverVisitor)); - commentReferenceRemoverVisitor.run(); - commentRangeStartRemoverVisitor.run(); - commentRangeEndRemoverVisitor.run(); - } - - private static List getCommentElements(WordprocessingMLPackage document) { - var commentFinder = new CommentFinder(); - TraversalUtil.visit(document, true, commentFinder); - return commentFinder.getCommentElements(); + var crVisitor = new CommentReferenceRemoverVisitor(malformedCommentIds); + var crsVisitor = new CommentRangeStartRemoverVisitor(malformedCommentIds); + var creVisitor = new CommentRangeEndRemoverVisitor(malformedCommentIds); + TraversalUtil.visit(document, true, List.of(crVisitor, crsVisitor, creVisitor)); + crVisitor.run(); + crsVisitor.run(); + creVisitor.run(); } private static Comments tryGetCommentsPart(CommentsPart commentsPart) { @@ -104,4 +91,9 @@ private static Comments tryGetCommentsPart(CommentsPart commentsPart) { } } + private static boolean isEmpty(Comments.Comment c) { + var content = c.getContent(); + return content == null || content.isEmpty(); + } + } diff --git a/engine/src/main/java/pro/verron/officestamper/utils/WmlUtils.java b/engine/src/main/java/pro/verron/officestamper/utils/WmlUtils.java index ccec0443..e312f29f 100644 --- a/engine/src/main/java/pro/verron/officestamper/utils/WmlUtils.java +++ b/engine/src/main/java/pro/verron/officestamper/utils/WmlUtils.java @@ -1,8 +1,12 @@ package pro.verron.officestamper.utils; +import org.docx4j.TraversalUtil; +import org.docx4j.finders.CommentFinder; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.jvnet.jaxb2_commons.ppp.Child; import pro.verron.officestamper.api.OfficeStamperException; +import java.util.List; import java.util.Optional; public final class WmlUtils { @@ -21,4 +25,10 @@ public static Optional getFirstParentWithClass(Child child, Class aCla } return Optional.empty(); } + + public static List extractCommentElements(WordprocessingMLPackage document) { + var commentFinder = new CommentFinder(); + TraversalUtil.visit(document, true, commentFinder); + return commentFinder.getCommentElements(); + } } From ff0d1affb8ecda02afa92361725223132e43d9db Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Mon, 4 Nov 2024 23:18:33 +0800 Subject: [PATCH 3/7] refactor: extract and streamline comment processing logic Extract comment processing logic into separate methods to improve readability and maintainability. Simplify method signatures and reduce redundancy. --- .../core/CommentProcessorRegistry.java | 179 ++++++++++-------- 1 file changed, 96 insertions(+), 83 deletions(-) diff --git a/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java b/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java index 1f729e8c..c8b11a3c 100644 --- a/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java +++ b/engine/src/main/java/pro/verron/officestamper/core/CommentProcessorRegistry.java @@ -1,5 +1,6 @@ package pro.verron.officestamper.core; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.wml.*; import org.jvnet.jaxb2_commons.ppp.Child; import org.slf4j.Logger; @@ -54,70 +55,12 @@ public CommentProcessorRegistry( this.exceptionResolver = exceptionResolver; } - private static Map collectComments(DocxPart document) { - var rootComments = new HashMap(); - var allComments = new HashMap(); - var stack = Collections.asLifoQueue(new ArrayDeque()); - - var list = WmlUtils.extractCommentElements(document.document()); - for (Child commentElement : list) { - if (commentElement instanceof CommentRangeStart crs) { - Comment comment = allComments.get(crs.getId()); - if (comment == null) { - comment = new StandardComment(document.document()); - allComments.put(crs.getId(), comment); - if (stack.isEmpty()) { - rootComments.put(crs.getId(), comment); - } - else { - stack.peek() - .getChildren() - .add(comment); - } - } - comment.setCommentRangeStart(crs); - stack.add(comment); - } - else if (commentElement instanceof CommentRangeEnd cre) { - Comment comment = allComments.get(cre.getId()); - if (comment == null) - throw new OfficeStamperException("Found a comment range end before the comment range start !"); - - comment.setCommentRangeEnd(cre); - - if (!stack.isEmpty()) { - var peek = stack.peek(); - if (peek.equals(comment)) stack.remove(); - else throw new OfficeStamperException("Cannot figure which comment contains the other !"); - } - } - else if (commentElement instanceof R.CommentReference cr) { - Comment comment = allComments.get(cr.getId()); - if (comment == null) { - comment = new StandardComment(document.document()); - allComments.put(cr.getId(), comment); - } - comment.setCommentReference(cr); - } - } - var sourceDocument = document.document(); - CommentUtil.getCommentsPart(sourceDocument.getParts()) - .map(CommentUtil::extractContent) - .map(Comments::getComment) - .stream() - .flatMap(Collection::stream) - .filter(comment -> allComments.containsKey(comment.getId())) - .forEach(comment -> allComments.get(comment.getId()) - .setComment(comment)); - return new HashMap<>(rootComments); - } - public void runProcessors(T expressionContext) { var proceedComments = new ArrayList(); source.streamRun() .forEach(run -> { - var comments = collectComments(source); + var comments = collectComments(); var runParent = StandardParagraph.from(source, (P) run.getParent()); var optional = runProcessorsOnRunComment(comments, expressionContext, run, runParent); commentProcessors.commitChanges(source); @@ -127,7 +70,7 @@ public void runProcessors(T expressionContext) { // we run the paragraph afterward so that the comments inside work before the whole paragraph comments source.streamParagraphs() .forEach(p -> { - var comments = collectComments(source); + var comments = collectComments(); var paragraphComment = p.getComment(); paragraphComment.ifPresent((pc -> { var optional = runProcessorsOnParagraphComment(comments, expressionContext, p, pc.getId()); @@ -142,18 +85,42 @@ public void runProcessors(T expressionContext) { proceedComments.forEach(CommentUtil::deleteComment); } + private Map collectComments() { + var rootComments = new HashMap(); + var allComments = new HashMap(); + var stack = Collections.asLifoQueue(new ArrayDeque()); + + var list = WmlUtils.extractCommentElements(document()); + for (Child commentElement : list) { + if (commentElement instanceof CommentRangeStart crs) onRangeStart(crs, allComments, stack, rootComments); + else if (commentElement instanceof CommentRangeEnd cre) onRangeEnd(cre, allComments, stack); + else if (commentElement instanceof R.CommentReference cr) onReference(cr, allComments); + } + CommentUtil.getCommentsPart(document().getParts()) + .map(CommentUtil::extractContent) + .map(Comments::getComment) + .stream() + .flatMap(Collection::stream) + .filter(comment -> allComments.containsKey(comment.getId())) + .forEach(comment -> allComments.get(comment.getId()) + .setComment(comment)); + return new HashMap<>(rootComments); + } + private Optional runProcessorsOnRunComment( Map comments, T expressionContext, R run, Paragraph paragraph ) { - return CommentUtil.getCommentAround(run, source.document()) + return CommentUtil.getCommentAround(run, document()) .flatMap(c -> Optional.ofNullable(comments.get(c.getId()))) .flatMap(c -> { - var context = new ProcessorContext(paragraph, run, c, c.asPlaceholder()); - commentProcessors.setContext(context); - var comment = runCommentProcessors(expressionContext, c); - comments.remove(c.getComment() - .getId()); - return comment; + var cPlaceholder = c.asPlaceholder(); + var cComment = c.getComment(); + comments.remove(cComment.getId()); + commentProcessors.setContext(new ProcessorContext(paragraph, run, c, cPlaceholder)); + return runCommentProcessors(expressionContext, cPlaceholder) + ? Optional.of(c) + : Optional.empty(); + }); } @@ -167,21 +134,16 @@ private Optional runProcessorsOnRunComment( * @param the type of the context root object. */ private Optional runProcessorsOnParagraphComment( - Map comments, - T expressionContext, - Paragraph paragraph, - BigInteger paragraphCommentId + Map comments, T expressionContext, Paragraph paragraph, BigInteger paragraphCommentId ) { if (!comments.containsKey(paragraphCommentId)) return Optional.empty(); var c = comments.get(paragraphCommentId); var cPlaceholder = c.asPlaceholder(); var cComment = c.getComment(); - var context = new ProcessorContext(paragraph, null, c, cPlaceholder); - commentProcessors.setContext(context); - var comment = runCommentProcessors(expressionContext, c); comments.remove(cComment.getId()); - return comment; + commentProcessors.setContext(new ProcessorContext(paragraph, null, c, cPlaceholder)); + return runCommentProcessors(expressionContext, c.asPlaceholder()) ? Optional.of(c) : Optional.empty(); } /** @@ -212,17 +174,68 @@ private void runProcessorsOnInlineContent(T context, Paragraph paragraph) { } } - private Optional runCommentProcessors(T context, Comment comment) { - var placeholder = comment.asPlaceholder(); + private WordprocessingMLPackage document() { + return source.document(); + } + + private void onRangeStart( + CommentRangeStart crs, + HashMap allComments, + Queue stack, + HashMap rootComments + ) { + Comment comment = allComments.get(crs.getId()); + if (comment == null) { + comment = new StandardComment(document()); + allComments.put(crs.getId(), comment); + if (stack.isEmpty()) { + rootComments.put(crs.getId(), comment); + } + else { + stack.peek() + .getChildren() + .add(comment); + } + } + comment.setCommentRangeStart(crs); + stack.add(comment); + } + + private void onRangeEnd( + CommentRangeEnd cre, HashMap allComments, Queue stack + ) { + Comment comment = allComments.get(cre.getId()); + if (comment == null) + throw new OfficeStamperException("Found a comment range end before the comment range start !"); + + comment.setCommentRangeEnd(cre); + + if (!stack.isEmpty()) { + var peek = stack.peek(); + if (peek.equals(comment)) stack.remove(); + else throw new OfficeStamperException("Cannot figure which comment contains the other !"); + } + } + + private void onReference(R.CommentReference cr, HashMap allComments) { + Comment comment = allComments.get(cr.getId()); + if (comment == null) { + comment = new StandardComment(document()); + allComments.put(cr.getId(), comment); + } + comment.setCommentReference(cr); + } + + private boolean runCommentProcessors(T context, Placeholder commentPlaceholder) { try { expressionResolver.setContext(context); - expressionResolver.resolve(placeholder); - logger.debug("Comment '{}' successfully processed by a comment processor.", placeholder); - return Optional.of(comment); + expressionResolver.resolve(commentPlaceholder); + logger.debug("Comment '{}' successfully processed by a comment processor.", commentPlaceholder); + return true; } catch (SpelEvaluationException | SpelParseException e) { - var message = "Comment '%s' failed to process.".formatted(placeholder.expression()); - exceptionResolver.resolve(placeholder, message, e); - return Optional.empty(); + var message = "Comment '%s' failed to process.".formatted(commentPlaceholder.expression()); + exceptionResolver.resolve(commentPlaceholder, message, e); + return false; } } From c3f21795e87850c7447bb265513299c940b779c6 Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Mon, 4 Nov 2024 23:45:25 +0800 Subject: [PATCH 4/7] refactor: remove DocumentWalker and integrate docx4j TraversalUtil Use docx4j TraversalUtil and ClassFinder to replace expressions in paragraphs. This change simplifies the codebase by removing redundant classes and leveraging library utilities. --- .../core/BaseDocumentWalker.java | 55 ------ .../officestamper/core/DocumentWalker.java | 159 ------------------ .../ParagraphResolverDocumentWalker.java | 48 ------ .../processors/repeat/RepeatProcessor.java | 20 ++- 4 files changed, 14 insertions(+), 268 deletions(-) delete mode 100644 engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java delete mode 100644 engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java delete mode 100644 engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/ParagraphResolverDocumentWalker.java diff --git a/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java b/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java deleted file mode 100644 index 73457fe1..00000000 --- a/engine/src/main/java/pro/verron/officestamper/core/BaseDocumentWalker.java +++ /dev/null @@ -1,55 +0,0 @@ -package pro.verron.officestamper.core; - -import org.docx4j.wml.CommentRangeEnd; -import org.docx4j.wml.CommentRangeStart; -import org.docx4j.wml.P; -import org.docx4j.wml.R; -import pro.verron.officestamper.api.DocxPart; - -/** - * This class is an abstract implementation of the {@link DocumentWalker} interface. - * It implements all methods of the interface and does nothing in the individual methods. - * This makes it easier to implement a custom {@link DocumentWalker} because the implementor - * only has to implement the methods that are of interest. - * - * @author Joseph Verron - * @author Tom Hombergs - * @version ${version} - * @since 1.0.0 - */ -public abstract class BaseDocumentWalker extends DocumentWalker { - - /** - * Creates a new document walker that walks through the given document. - * - */ - protected BaseDocumentWalker(DocxPart contentAccessor) { - super(contentAccessor); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onParagraph(P paragraph) { - - } - - /** {@inheritDoc} */ - @Override - protected void onCommentRangeStart(CommentRangeStart commentRangeStart) { - - } - - /** {@inheritDoc} */ - @Override - protected void onCommentRangeEnd(CommentRangeEnd commentRangeEnd) { - - } - - /** {@inheritDoc} */ - @Override - protected void onCommentReference(R.CommentReference commentReference) { - - } -} diff --git a/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java b/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java deleted file mode 100644 index 52a9a38c..00000000 --- a/engine/src/main/java/pro/verron/officestamper/core/DocumentWalker.java +++ /dev/null @@ -1,159 +0,0 @@ -package pro.verron.officestamper.core; - -import org.docx4j.XmlUtils; -import org.docx4j.wml.*; -import org.docx4j.wml.R.CommentReference; -import pro.verron.officestamper.api.DocxPart; - -/** - * This class walks the document and calls abstract methods for each element it encounters. - * The following elements are supported: - *
    - *
  • {@link P}
  • - *
  • {@link R}
  • - *
  • {@link Tbl}
  • - *
  • {@link Tr}
  • - *
  • {@link Tc}
  • - *
  • {@link CommentRangeStart}
  • - *
  • {@link CommentRangeEnd}
  • - *
  • {@link CommentReference}
  • - *
- * The following elements are not supported: - *
    - *
  • {@link SdtBlock}
  • - *
  • {@link SdtRun}
  • - *
  • {@link SdtElement}
  • - *
  • {@link CTSimpleField}
  • - *
  • {@link CTSdtCell}
  • - *
  • {@link CTSdtContentCell}
  • - *
- * - * @author Joseph Verron - * @author Tom Hombergs - * @version ${version} - * @since 1.0.0 - */ -public abstract class DocumentWalker { - - private final DocxPart source; - - /** - * Creates a new DocumentWalker that will traverse the given document. - * - * @param source the document to traverse. - */ - protected DocumentWalker(DocxPart source) { - this.source = source; - } - - /** - * Starts the traversal of the document. - */ - public void walk() { - for (Object content : source.content()) { - Object ue = XmlUtils.unwrap(content); - if (ue instanceof P o) walkParagraph(o); - else if (ue instanceof R o) walkRun(o); - else if (ue instanceof Tbl o) walkTable(o); - else if (ue instanceof Tr o) walkTableRow(o); - else if (ue instanceof Tc o) walkTableCell(o); - else if (ue instanceof CommentRangeStart o) onCommentRangeStart(o); - else if (ue instanceof CommentRangeEnd o) onCommentRangeEnd(o); - else if (ue instanceof CommentReference o) onCommentReference(o); - } - } - - private void walkTable(Tbl table) { - for (Object contentElement : table.getContent()) { - Object unwrappedObject = XmlUtils.unwrap(contentElement); - if (unwrappedObject instanceof Tr row) { - walkTableRow(row); - } - } - } - - private void walkTableRow(Tr row) { - for (Object rowContentElement : row.getContent()) { - Object unwrappedObject = XmlUtils.unwrap(rowContentElement); - if (unwrappedObject instanceof Tc cell) { - walkTableCell(cell); - } - } - } - - private void walkTableCell(Tc cell) { - for (Object cellContentElement : cell.getContent()) { - Object unwrappedObject = XmlUtils.unwrap(cellContentElement); - if (unwrappedObject instanceof P) { - P p = (P) cellContentElement; - walkParagraph(p); - } - else if (unwrappedObject instanceof R) { - R r = (R) cellContentElement; - walkRun(r); - } - else if (unwrappedObject instanceof Tbl nestedTable) { - walkTable(nestedTable); - } - else if (unwrappedObject instanceof CommentRangeStart commentRangeStart) { - onCommentRangeStart(commentRangeStart); - } - else if (unwrappedObject instanceof CommentRangeEnd commentRangeEnd) { - onCommentRangeEnd(commentRangeEnd); - } - } - } - - private void walkParagraph(P p) { - onParagraph(p); - for (Object element : p.getContent()) { - Object unwrappedObject = XmlUtils.unwrap(element); - if (unwrappedObject instanceof R r) { - walkRun(r); - } - else if (unwrappedObject instanceof CommentRangeStart commentRangeStart) { - onCommentRangeStart(commentRangeStart); - } - else if (unwrappedObject instanceof CommentRangeEnd commentRangeEnd) { - onCommentRangeEnd(commentRangeEnd); - } - } - } - - private void walkRun(R r) { - for (Object element : r.getContent()) { - Object unwrappedObject = XmlUtils.unwrap(element); - if (unwrappedObject instanceof CommentReference commentReference) { - onCommentReference(commentReference); - } - } - } - - /** - * This method is called for every {@link P} element in the document. - * - * @param paragraph the {@link P} element to process. - */ - protected abstract void onParagraph(P paragraph); - - /** - * This method is called for every {@link CommentRangeStart} element in the document. - * - * @param commentRangeStart the {@link CommentRangeStart} element to process. - */ - protected abstract void onCommentRangeStart(CommentRangeStart commentRangeStart); - - /** - * This method is called for every {@link CommentRangeEnd} element in the document. - * - * @param commentRangeEnd the {@link CommentRangeEnd} element to process. - */ - protected abstract void onCommentRangeEnd(CommentRangeEnd commentRangeEnd); - - /** - * This method is called for every {@link CommentReference} element in the document. - * - * @param commentReference the {@link CommentReference} element to process. - */ - protected abstract void onCommentReference(CommentReference commentReference); -} diff --git a/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/ParagraphResolverDocumentWalker.java b/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/ParagraphResolverDocumentWalker.java deleted file mode 100644 index 9ff0f22c..00000000 --- a/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/ParagraphResolverDocumentWalker.java +++ /dev/null @@ -1,48 +0,0 @@ -package pro.verron.officestamper.preset.processors.repeat; - -import org.docx4j.wml.P; -import org.docx4j.wml.Tr; -import pro.verron.officestamper.api.DocxPart; -import pro.verron.officestamper.api.ParagraphPlaceholderReplacer; -import pro.verron.officestamper.core.BaseDocumentWalker; -import pro.verron.officestamper.core.StandardParagraph; - -/** - * Walks through a document and replaces expressions with values from the given - * expression context. - * This walker only replaces expressions in paragraphs, not in tables. - * - * @author Joseph Verron - * @version ${version} - * @since 1.4.7 - */ -class ParagraphResolverDocumentWalker - extends BaseDocumentWalker { - private final Object expressionContext; - private final DocxPart docxPart; - private final ParagraphPlaceholderReplacer placeholderReplacer; - - /** - *

Constructor for ParagraphResolverDocumentWalker.

- * - * @param rowClone The row to start with - * @param expressionContext The context of the expressions to resolve - * @param replacer The placeholderReplacer to use for resolving - */ - public ParagraphResolverDocumentWalker( - DocxPart docxPart, Tr rowClone, Object expressionContext, ParagraphPlaceholderReplacer replacer - ) { - super(docxPart.from(rowClone)); - this.expressionContext = expressionContext; - this.docxPart = docxPart; - this.placeholderReplacer = replacer; - } - - /** - * {@inheritDoc} - */ - @Override protected void onParagraph(P paragraph) { - var standardParagraph = StandardParagraph.from(docxPart, paragraph); - placeholderReplacer.resolveExpressionsForParagraph(docxPart, standardParagraph, expressionContext); - } -} diff --git a/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/RepeatProcessor.java b/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/RepeatProcessor.java index 59f03f1c..2aa66b84 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/RepeatProcessor.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/processors/repeat/RepeatProcessor.java @@ -1,13 +1,17 @@ package pro.verron.officestamper.preset.processors.repeat; +import org.docx4j.TraversalUtil; import org.docx4j.XmlUtils; +import org.docx4j.finders.ClassFinder; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.wml.Comments; +import org.docx4j.wml.P; import org.docx4j.wml.Tbl; import org.docx4j.wml.Tr; import org.springframework.lang.Nullable; import pro.verron.officestamper.api.*; import pro.verron.officestamper.core.CommentUtil; +import pro.verron.officestamper.core.StandardParagraph; import pro.verron.officestamper.preset.CommentProcessorFactory; import java.math.BigInteger; @@ -82,10 +86,14 @@ private void repeatRows(DocxPart source) { Comments.Comment comment = requireNonNull(commentWrapper.getComment()); BigInteger commentId = comment.getId(); CommentUtil.deleteCommentFromElements(rowClone.getContent(), commentId); - new ParagraphResolverDocumentWalker(source, - rowClone, - expressionContext, - this.placeholderReplacer).walk(); + var classFinder = new ClassFinder(P.class); + TraversalUtil.visit(rowClone, classFinder); + var objects = classFinder.results; + for (Object object : objects) { + P result = (P) object; + StandardParagraph paragraph = StandardParagraph.from(source, result); + placeholderReplacer.resolveExpressionsForParagraph(source, paragraph, expressionContext); + } changes.add(rowClone); } } @@ -102,8 +110,8 @@ private void repeatRows(DocxPart source) { /** {@inheritDoc} */ @Override public void repeatTableRow(@Nullable List objects) { var tr = this.getParagraph() - .parent(Tr.class) - .orElseThrow(OfficeStamperException.throwing("This paragraph is not in a table row.")); + .parent(Tr.class) + .orElseThrow(OfficeStamperException.throwing("This paragraph is not in a table row.")); tableRowsToRepeat.put(tr, objects); tableRowsCommentsToRemove.put(tr, getCurrentCommentWrapper()); } From 3a72106047c4d0a47cd9eda6f6163f3373ea23b2 Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Tue, 5 Nov 2024 00:05:18 +0800 Subject: [PATCH 5/7] refactor: remove unused static import for WmlFactory.newRun Simplify codebase by eliminating redundant imports to enhance readability and maintainability. --- .../main/java/pro/verron/officestamper/preset/Resolvers.java | 5 ++--- .../preset/resolvers/nulls/Null2DefaultResolver.java | 1 - .../preset/resolvers/nulls/Null2PlaceholderResolver.java | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/engine/src/main/java/pro/verron/officestamper/preset/Resolvers.java b/engine/src/main/java/pro/verron/officestamper/preset/Resolvers.java index 7a4641f4..9bbda668 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/Resolvers.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/Resolvers.java @@ -1,6 +1,7 @@ package pro.verron.officestamper.preset; -import pro.verron.officestamper.api.*; +import pro.verron.officestamper.api.ObjectResolver; +import pro.verron.officestamper.api.OfficeStamperException; import pro.verron.officestamper.preset.resolvers.date.DateResolver; import pro.verron.officestamper.preset.resolvers.image.ImageResolver; import pro.verron.officestamper.preset.resolvers.localdate.LocalDateResolver; @@ -16,8 +17,6 @@ import java.time.format.DateTimeFormatter; import java.util.Date; -import static pro.verron.officestamper.utils.WmlFactory.newRun; - /** * This class provides static methods to create different types of * {@link ObjectResolver}. diff --git a/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2DefaultResolver.java b/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2DefaultResolver.java index 69cd8739..c9124bf1 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2DefaultResolver.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2DefaultResolver.java @@ -4,7 +4,6 @@ import org.springframework.lang.Nullable; import pro.verron.officestamper.api.DocxPart; import pro.verron.officestamper.api.ObjectResolver; -import pro.verron.officestamper.preset.Resolvers; import static pro.verron.officestamper.utils.WmlFactory.newRun; diff --git a/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2PlaceholderResolver.java b/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2PlaceholderResolver.java index 8d4b4a9c..18bf4f1a 100644 --- a/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2PlaceholderResolver.java +++ b/engine/src/main/java/pro/verron/officestamper/preset/resolvers/nulls/Null2PlaceholderResolver.java @@ -6,7 +6,6 @@ import pro.verron.officestamper.api.ObjectResolver; import pro.verron.officestamper.api.OfficeStamperException; import pro.verron.officestamper.api.Placeholder; -import pro.verron.officestamper.preset.Resolvers; import static pro.verron.officestamper.utils.WmlFactory.newRun; From 1aaf59644082837542d1c3625b43ae356cf4079b Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Fri, 8 Nov 2024 00:17:22 +0800 Subject: [PATCH 6/7] refactor: extract and reorganize stringify methods in Stringifier Encapsulate specific stringify logic into dedicated methods for cleaner code structure. --- .../officestamper/test/DefaultTests.java | 42 +-- .../officestamper/test/MultiSectionTest.java | 2 +- .../officestamper/test/Stringifier.java | 354 +++++++++--------- 3 files changed, 200 insertions(+), 198 deletions(-) diff --git a/engine/src/test/java/pro/verron/officestamper/test/DefaultTests.java b/engine/src/test/java/pro/verron/officestamper/test/DefaultTests.java index b8aebe81..91a3e866 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/DefaultTests.java +++ b/engine/src/test/java/pro/verron/officestamper/test/DefaultTests.java @@ -717,7 +717,7 @@ private static Arguments changingPageLayoutTest_shouldKeepSectionBreakOrientatio First page is landscape. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< Second page is portrait, layout change should survive to repeatParagraph processor (Homer). @@ -732,7 +732,7 @@ Second page is portrait, layout change should survive to repeatParagraph process <<< - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Fourth page is set to landscape again. """); @@ -745,24 +745,24 @@ private static Arguments changingPageLayoutTest_shouldKeepSectionBreakOrientatio First page is landscape. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< Second page is portrait, layout change should survive to repeatParagraph processor (Homer). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< With a page break changing the layout in between. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< Second page is portrait, layout change should survive to repeatParagraph processor (Marge). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< With a page break changing the layout in between. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< Fourth page is set to portrait again. """; @@ -784,26 +784,26 @@ private static Arguments changingPageLayoutTest_shouldKeepPageBreakOrientationIn First page is portrait. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Second page is landscape, layout change should survive to repeatDocPart (Homer). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< With a break setting the layout to portrait in between. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Second page is landscape, layout change should survive to repeatDocPart (Marge). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< With a break setting the layout to portrait in between. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Fourth page is set to landscape again. """); @@ -830,12 +830,12 @@ private static Arguments changingPageLayoutTest_shouldKeepPageBreakOrientationIn First page is portrait. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Second page is landscape, layout change should survive to repeatDocPart (Homer). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< With a break setting the layout to portrait in between. |=== @@ -844,12 +844,12 @@ Second page is landscape, layout change should survive to repeatDocPart (Homer). |=== - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Second page is landscape, layout change should survive to repeatDocPart (Marge). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< With a break setting the layout to portrait in between. |=== @@ -858,10 +858,10 @@ Second page is landscape, layout change should survive to repeatDocPart (Marge). |=== - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Fourth page is set to landscape again. """); @@ -876,7 +876,7 @@ private static Arguments changingPageLayoutTest_shouldKeepPageBreakOrientationIn First page is landscape. - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=11906,orient=LANDSCAPE,w=16838}}] <<< Second page is portrait, layout change should survive to repeatDocPart (Homer). @@ -891,7 +891,7 @@ Second page is portrait, layout change should survive to repeatDocPart (Marge). Without a break changing the layout in between (page break should be repeated). - [section-break, docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1418,footer=709,gutter=0,header=709,left=1418,right=1418,top=1418},pgSz={h=16838,w=11906}}] <<< Fourth page is set to landscape again. """); diff --git a/engine/src/test/java/pro/verron/officestamper/test/MultiSectionTest.java b/engine/src/test/java/pro/verron/officestamper/test/MultiSectionTest.java index 837f9e74..ef420890 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/MultiSectionTest.java +++ b/engine/src/test/java/pro/verron/officestamper/test/MultiSectionTest.java @@ -22,7 +22,7 @@ void expressionsInMultipleSections() { Homer - [section-break, docGrid={linePitch=360},pgMar={bottom=1417,footer=708,gutter=0,header=708,left=1417,right=1417,top=1417},pgSz={h=16838,w=11906}] + [section-break, {docGrid={linePitch=360},pgMar={bottom=1417,footer=708,gutter=0,header=708,left=1417,right=1417,top=1417},pgSz={h=16838,w=11906}}] <<< Marge """; diff --git a/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java b/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java index 302fa1a9..5610f752 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java +++ b/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java @@ -40,7 +40,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; -import static java.util.Optional.ofNullable; +import static java.util.Optional.*; import static java.util.stream.Collectors.joining; /** @@ -62,10 +62,9 @@ public class Stringifier { */ public Stringifier(Supplier documentSupplier) { this.documentSupplier = documentSupplier; - this.styleDefinitionsPartSupplier = - () -> documentSupplier.get() - .getMainDocumentPart() - .getStyleDefinitionsPart(true); + this.styleDefinitionsPartSupplier = () -> documentSupplier.get() + .getMainDocumentPart() + .getStyleDefinitionsPart(true); } public static String stringifyPowerpoint(PresentationMLPackage presentation) { @@ -129,14 +128,24 @@ private static Predicate idEqual(BigInteger id) { }; } - private static void extract( - Map map, String key, Object value + + private static String stringify( + Map map, String affectation, String delimiter, String prefix, String suffix ) { - if (value != null) map.put(key, value); + return map.entrySet() + .stream() + .map(e -> e.getKey() + affectation + e.getValue()) + .collect(joining(delimiter, prefix, suffix)); } - private static Function, String> format(String format) { - return entry -> format.formatted(entry.getKey(), entry.getValue()); + private static Optional stringify( + List list, Function> stringify, String delimiter, String prefix, String suffix + ) { + if (list == null) return empty(); + return of(list.stream() + .map(stringify) + .flatMap(Optional::stream) + .collect(joining(delimiter, prefix, suffix))); } private String stringify(Text text) { @@ -228,16 +237,7 @@ private String sha1b64(byte[] imageBytes) { */ public String stringify(Object o) { if (o instanceof JAXBElement jaxb) return stringify(jaxb.getValue()); - if (o instanceof WordprocessingMLPackage mlPackage) { - var header = stringifyHeaders(getHeaderPart(mlPackage)); - var body = stringify(mlPackage.getMainDocumentPart()); - var footer = stringifyFooters(getFooterPart(mlPackage)); - var hStr = header.map(h -> h + "\n\n") - .orElse(""); - var fStr = footer.map(f -> "\n" + f + "\n") - .orElse(""); - return hStr + body + fStr; - } + if (o instanceof WordprocessingMLPackage mlPackage) return stringify(mlPackage); if (o instanceof Tbl tbl) return stringify(tbl); if (o instanceof Tr tr) return stringify(tr); if (o instanceof Tc tc) return stringify(tc); @@ -245,7 +245,7 @@ public String stringify(Object o) { if (o instanceof Body body) return stringify(body.getContent()); if (o instanceof List list) return stringify(list); if (o instanceof Text text) return stringify(text); - if (o instanceof P p) return stringify(p) + "\n"; + if (o instanceof P p) return stringify(p); if (o instanceof R r) return stringify(r); if (o instanceof Drawing drawing) return stringify(drawing); if (o instanceof Inline inline) return stringify(inline); @@ -263,21 +263,43 @@ public String stringify(Object o) { if (o instanceof ProofErr) return ""; if (o instanceof CommentRangeStart) return ""; if (o instanceof CommentRangeEnd) return ""; - if (o instanceof SdtBlock block) return stringify(block.getSdtContent()) + "\n"; + if (o instanceof SdtBlock block) return stringify(block); if (o instanceof AlternateContent) return ""; if (o instanceof Pict pict) return stringify(pict.getAnyAndAny()); if (o instanceof CTShapetype) return ""; - if (o instanceof VmlShapeElements vmlShapeElements) - return "[" + stringify(vmlShapeElements.getEGShapeElements()).trim() + "]\n"; + if (o instanceof VmlShapeElements vmlShapeElements) return stringify(vmlShapeElements); if (o instanceof CTTextbox ctTextbox) return stringify(ctTextbox.getTxbxContent()); if (o instanceof CTTxbxContent content) return stringify(content.getContent()); if (o instanceof CTShadow) return ""; if (o instanceof SdtRun run) return stringify(run.getSdtContent()); - if (o instanceof SdtContent content) return "[" + stringify(content.getContent()).trim() + "]"; + if (o instanceof SdtContent content) return stringify(content); if (o == null) throw new RuntimeException("Unsupported content: NULL"); throw new RuntimeException("Unsupported content: " + o.getClass()); } + private String stringify(SdtBlock block) { + return stringify(block.getSdtContent()) + "\n"; + } + + private String stringify(SdtContent content) { + return "[" + stringify(content.getContent()).trim() + "]"; + } + + private String stringify(VmlShapeElements vmlShapeElements) { + return "[" + stringify(vmlShapeElements.getEGShapeElements()).trim() + "]\n"; + } + + private String stringify(WordprocessingMLPackage mlPackage) { + var header = stringifyHeaders(getHeaderPart(mlPackage)); + var body = stringify(mlPackage.getMainDocumentPart()); + var footer = stringifyFooters(getFooterPart(mlPackage)); + var hStr = header.map(h -> h + "\n\n") + .orElse(""); + var fStr = footer.map(f -> "\n" + f + "\n") + .orElse(""); + return hStr + body + fStr; + } + private String stringify(Tc tc) { var content = stringify(tc.getContent()); return """ @@ -315,8 +337,8 @@ private Optional stringifyHeaders(Stream headerPart) { private Optional stringify(HeaderPart part) { var content = stringify(part.getContent()); - if (content.isEmpty()) return Optional.empty(); - return Optional.of(""" + if (content.isEmpty()) return empty(); + return of(""" [header, name="%s"] ---- %s @@ -325,15 +347,14 @@ private Optional stringify(HeaderPart part) { private Optional stringify(FooterPart part) { var content = stringify(part.getContent()); - if (content.isEmpty()) return Optional.empty(); - return Optional.of(""" + if (content.isEmpty()) return empty(); + return of(""" [footer, name="%s"] ---- %s ----""".formatted(part.getPartName(), content)); } - private Stream getHeaderPart(WordprocessingMLPackage document) { var sections = document.getDocumentModel() .getSections(); @@ -392,8 +413,9 @@ private String stringify(CTBlipFillProperties blipFillProperties) { private String stringify(R.CommentReference commentReference) { try { - return findComment(document(), commentReference.getId()).map(c -> stringify(c.getContent())) - .orElseThrow(); + return Stringifier.findComment(document(), commentReference.getId()) + .map(c -> stringify(c.getContent())) + .orElseThrow(); } catch (Docx4JException e) { throw new RuntimeException(e); } @@ -433,20 +455,15 @@ private String stringify(List list) { * @since 1.6.6 */ private Optional stringify(PPrBase.Spacing spacing) { - if (spacing == null) return Optional.empty(); - SortedMap map = new TreeMap<>(); - extract(map, "after", spacing.getAfter()); - extract(map, "before", spacing.getBefore()); - extract(map, "beforeLines", spacing.getBeforeLines()); - extract(map, "afterLines", spacing.getAfterLines()); - extract(map, "line", spacing.getLine()); - extract(map, "lineRule", spacing.getLineRule()); - return map.isEmpty() - ? Optional.empty() - : Optional.of(map.entrySet() - .stream() - .map(format("%s=%s")) - .collect(joining(",", "{", "}"))); + if (spacing == null) return empty(); + var map = new TreeMap(); + ofNullable(spacing.getAfter()).ifPresent(value -> map.put("after", String.valueOf(value))); + ofNullable(spacing.getBefore()).ifPresent(value -> map.put("before", String.valueOf(value))); + ofNullable(spacing.getBeforeLines()).ifPresent(value -> map.put("beforeLines", String.valueOf(value))); + ofNullable(spacing.getAfterLines()).ifPresent(value -> map.put("afterLines", String.valueOf(value))); + ofNullable(spacing.getLine()).ifPresent(value -> map.put("line", String.valueOf(value))); + ofNullable(spacing.getLineRule()).ifPresent(value -> map.put("lineRule", value.value())); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); } /** @@ -461,7 +478,7 @@ private Optional stringify(PPrBase.Spacing spacing) { private String stringify(P p) { var runs = stringify(p.getContent()); var ppr = stringify(p.getPPr()); - return ppr.apply(runs); + return ppr.apply(runs) + "\n"; } private Function stringify(PPr pPr) { @@ -501,13 +518,11 @@ private Function stringify(PPr pPr) { ofNullable(pPr.getCnfStyle()).ifPresent(style -> set.put("cnfStyle", style.getVal())); return set.entrySet() .stream() - .reduce(Function.identity(), - (f, entry) -> switch (entry.getKey()) { - case "pStyle" -> f.compose(decorateWithStyle(entry.getValue())); - case "sectPr" -> f.compose(str -> str+"\n[section-break, " + entry.getValue() +"]\n<<<"); - default -> f.andThen(s -> s + "<%s=%s>".formatted(entry.getKey(), entry.getValue())); - }, - Function::andThen); + .reduce(Function.identity(), (f, entry) -> switch (entry.getKey()) { + case "pStyle" -> f.compose(decorateWithStyle(entry.getValue())); + case "sectPr" -> f.compose(str -> str + "\n[section-break, " + entry.getValue() + "]\n<<<"); + default -> f.andThen(s -> s + "<%s=%s>".formatted(entry.getKey(), entry.getValue())); + }, Function::andThen); } private Function decorateWithStyle(String value) { @@ -551,130 +566,117 @@ private String stringify(R run) { * @since 1.6.6 */ private Optional stringify(RPrAbstract rPr) { - if (rPr == null) return Optional.empty(); - var set = new TreeSet(); - if (rPr.getB() != null) set.add("b=" + rPr.getB() - .isVal()); - if (rPr.getBdr() != null) set.add("bdr=xxx"); - if (rPr.getCaps() != null) set.add("caps=" + rPr.getCaps() - .isVal()); - if (rPr.getColor() != null) set.add("color=" + rPr.getColor() - .getVal()); - if (rPr.getDstrike() != null) set.add("dstrike=" + rPr.getDstrike() - .isVal()); - if (rPr.getI() != null) set.add("i=" + rPr.getI() - .isVal()); - if (rPr.getKern() != null) set.add("kern=" + rPr.getKern() - .getVal() - .intValue()); - if (rPr.getLang() != null) set.add("lang=" + rPr.getLang() - .getVal()); - if (rPr.getRFonts() != null) {/* DO NOTHING */} - if (rPr.getRPrChange() != null) set.add("rPrChange=xxx"); - if (rPr.getRStyle() != null) set.add("rStyle=" + rPr.getRStyle() - .getVal()); - if (rPr.getRtl() != null) set.add("rtl=" + rPr.getRtl() - .isVal()); - if (rPr.getShadow() != null) set.add("shadow=" + rPr.getShadow() - .isVal()); - if (rPr.getShd() != null) set.add("shd=" + rPr.getShd() - .getColor()); - if (rPr.getSmallCaps() != null) set.add("smallCaps=" + rPr.getSmallCaps() - .isVal()); - if (rPr.getVertAlign() != null) set.add("vertAlign=" + rPr.getVertAlign() - .getVal() - .value()); - if (rPr.getSpacing() != null) set.add("spacing=" + rPr.getSpacing() - .getVal() - .intValue()); - if (rPr.getStrike() != null) set.add("strike=" + rPr.getStrike() - .isVal()); - if (rPr.getOutline() != null) set.add("outline=" + rPr.getOutline() - .isVal()); - if (rPr.getEmboss() != null) set.add("emboss=" + rPr.getEmboss() - .isVal()); - if (rPr.getImprint() != null) set.add("imprint=" + rPr.getImprint() - .isVal()); - if (rPr.getNoProof() != null) set.add("noProof=" + rPr.getNoProof() - .isVal()); - if (rPr.getSpecVanish() != null) set.add("specVanish=" + rPr.getSpecVanish() - .isVal()); - if (rPr.getU() != null) set.add("u=" + rPr.getU() - .getVal() - .value()); - if (rPr.getVanish() != null) set.add("vanish=" + rPr.getVanish() - .isVal()); - if (rPr.getW() != null) set.add("w=" + rPr.getW() - .getVal()); - if (rPr.getWebHidden() != null) set.add("webHidden=" + rPr.getWebHidden() - .isVal()); - if (rPr.getHighlight() != null) set.add("highlight=" + rPr.getHighlight() - .getVal()); - if (rPr.getEffect() != null) set.add("effect=" + rPr.getEffect() - .getVal() - .value()); - if (set.isEmpty()) return Optional.empty(); - return Optional.of("{" + String.join(",", set) + "}"); + if (rPr == null) return empty(); + var map = new TreeMap(); + ofNullable(rPr.getB()).ifPresent(value -> map.put("b", String.valueOf(value.isVal()))); + ofNullable(rPr.getBdr()).ifPresent(value -> map.put("bdr", "xxx")); + ofNullable(rPr.getCaps()).ifPresent(value -> map.put("caps", String.valueOf(value.isVal()))); + ofNullable(rPr.getColor()).ifPresent(value -> map.put("color", value.getVal())); + ofNullable(rPr.getDstrike()).ifPresent(value -> map.put("dstrike", String.valueOf(value.isVal()))); + ofNullable(rPr.getI()).ifPresent(value -> map.put("i", String.valueOf(value.isVal()))); + ofNullable(rPr.getKern()).ifPresent(value -> map.put("kern", String.valueOf(value.getVal()))); + ofNullable(rPr.getLang()).ifPresent(value -> map.put("lang", value.getVal())); + stringify(rPr.getRFonts()).ifPresent(e -> map.put("rFont", e)); + ofNullable(rPr.getRPrChange()).ifPresent(value -> map.put("rPrChange", "xxx")); + ofNullable(rPr.getRStyle()).ifPresent(value -> map.put("rStyle", value.getVal())); + ofNullable(rPr.getRtl()).ifPresent(value -> map.put("rtl", String.valueOf(value.isVal()))); + ofNullable(rPr.getShadow()).ifPresent(value -> map.put("shadow", String.valueOf(value.isVal()))); + ofNullable(rPr.getShd()).ifPresent(value -> map.put("shd", value.getColor())); + ofNullable(rPr.getSmallCaps()).ifPresent(value -> map.put("smallCaps", String.valueOf(value.isVal()))); + ofNullable(rPr.getVertAlign()).ifPresent(value -> map.put("vertAlign", + value.getVal() + .value())); + ofNullable(rPr.getSpacing()).ifPresent(value -> map.put("spacing", String.valueOf(value.getVal()))); + ofNullable(rPr.getStrike()).ifPresent(value -> map.put("strike", String.valueOf(value.isVal()))); + ofNullable(rPr.getOutline()).ifPresent(value -> map.put("outline", String.valueOf(value.isVal()))); + ofNullable(rPr.getEmboss()).ifPresent(value -> map.put("emboss", String.valueOf(value.isVal()))); + ofNullable(rPr.getImprint()).ifPresent(value -> map.put("imprint", String.valueOf(value.isVal()))); + ofNullable(rPr.getNoProof()).ifPresent(value -> map.put("noProof", String.valueOf(value.isVal()))); + ofNullable(rPr.getSpecVanish()).ifPresent(value -> map.put("specVanish", String.valueOf(value.isVal()))); + ofNullable(rPr.getU()).ifPresent(value -> map.put("u", + value.getVal() + .value())); + ofNullable(rPr.getVanish()).ifPresent(value -> map.put("vanish", String.valueOf(value.isVal()))); + ofNullable(rPr.getW()).ifPresent(value -> map.put("w", String.valueOf(value.getVal()))); + ofNullable(rPr.getWebHidden()).ifPresent(value -> map.put("webHidden", String.valueOf(value.isVal()))); + ofNullable(rPr.getHighlight()).ifPresent(value -> map.put("highlight", value.getVal())); + ofNullable(rPr.getEffect()).ifPresent(value -> map.put("effect", + value.getVal() + .value())); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + } + + private Optional stringify(RFonts rFonts) { + if (rFonts == null) return empty(); + var map = new TreeMap(); + ofNullable(rFonts.getAscii()).ifPresent(value -> map.put("ascii", value)); + ofNullable(rFonts.getHAnsi()).ifPresent(value -> map.put("hAnsi", value)); + ofNullable(rFonts.getCs()).ifPresent(value -> map.put("cs", value)); + ofNullable(rFonts.getEastAsia()).ifPresent(value -> map.put("eastAsia", value)); + ofNullable(rFonts.getAsciiTheme()).ifPresent(value -> map.put("asciiTheme", value.value())); + ofNullable(rFonts.getHAnsiTheme()).ifPresent(value -> map.put("hAnsiTheme", value.value())); + ofNullable(rFonts.getCstheme()).ifPresent(value -> map.put("cstheme", value.value())); + ofNullable(rFonts.getEastAsiaTheme()).ifPresent(value -> map.put("eastAsiaTheme", value.value())); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); } private Optional stringify(SectPr sectPr) { - if (sectPr == null) return Optional.empty(); - var set = new TreeSet(); - if (sectPr.getEGHdrFtrReferences() != null && !sectPr.getEGHdrFtrReferences() - .isEmpty()) - set.add("eGHdrFtrReferences=%s".formatted(sectPr.getEGHdrFtrReferences() - .stream() - .map(this::stringify) - .collect(joining(",", "[", "]")))); - if (sectPr.getPgSz() != null) set.add("pgSz={" + stringify(sectPr.getPgSz()) + "}"); - if (sectPr.getPgMar() != null) set.add("pgMar={" + stringify(sectPr.getPgMar()) + "}"); - if (sectPr.getPaperSrc() != null) set.add("paperSrc=xxx"); - if (sectPr.getBidi() != null) set.add("bidi=xxx"); - if (sectPr.getRtlGutter() != null) set.add("rtlGutter=xxx"); - if (sectPr.getDocGrid() != null) set.add("docGrid={" + stringify(sectPr.getDocGrid()) + "}"); - if (sectPr.getFormProt() != null) set.add("formProt=xxx"); - if (sectPr.getVAlign() != null) set.add("vAlign=xxx"); - if (sectPr.getNoEndnote() != null) set.add("noEndnote=xxx"); - if (sectPr.getTitlePg() != null) set.add("titlePg=xxx"); - if (sectPr.getTextDirection() != null) set.add("textDirection=xxx"); - if (sectPr.getRtlGutter() != null) set.add("rtlGutter=xxx"); - if (set.isEmpty()) return Optional.empty(); - return Optional.of(String.join(",", set)); - } - - private String stringify(CTDocGrid ctDocGrid) { - var set = new TreeSet(); - if (ctDocGrid.getCharSpace() != null) set.add("charSpace=" + ctDocGrid.getCharSpace()); - if (ctDocGrid.getLinePitch() != null) set.add("linePitch=" + ctDocGrid.getLinePitch() - .intValue()); - if (ctDocGrid.getType() != null) set.add("type=" + ctDocGrid.getType()); - return String.join(",", set); - } - - private String stringify(CTRel ctRel) { - var set = new TreeSet(); - if (ctRel.getId() != null) set.add("id=" + ctRel.getId()); - return String.join(",", set); - } - - private String stringify(SectPr.PgMar pgMar) { - var set = new TreeSet(); - if (pgMar.getHeader() != null) set.add("header=" + pgMar.getHeader()); - if (pgMar.getFooter() != null) set.add("footer=" + pgMar.getFooter()); - if (pgMar.getGutter() != null) set.add("gutter=" + pgMar.getGutter()); - if (pgMar.getTop() != null) set.add("top=" + pgMar.getTop()); - if (pgMar.getLeft() != null) set.add("left=" + pgMar.getLeft()); - if (pgMar.getBottom() != null) set.add("bottom=" + pgMar.getBottom()); - if (pgMar.getRight() != null) set.add("right=" + pgMar.getRight()); - return String.join(",", set); - } - - private String stringify(SectPr.PgSz pgSz) { - var set = new TreeSet(); - if (pgSz.getOrient() != null) set.add("orient=" + pgSz.getOrient()); - if (pgSz.getW() != null) set.add("w=" + pgSz.getW()); - if (pgSz.getH() != null) set.add("h=" + pgSz.getH()); - if (pgSz.getCode() != null) set.add("code=" + pgSz.getCode()); - return String.join(",", set); + if (sectPr == null) return empty(); + var map = new TreeMap(); + stringify(sectPr.getEGHdrFtrReferences(), this::stringify, ",", "[", "]").ifPresent(value -> map.put( + "eGHdrFtrReferences", + value)); + stringify(sectPr.getPgSz()).ifPresent(value -> map.put("pgSz", value)); + stringify(sectPr.getPgMar()).ifPresent(value -> map.put("pgMar", value)); + ofNullable(sectPr.getPaperSrc()).ifPresent(value -> map.put("paperSrc", "xxx")); + ofNullable(sectPr.getBidi()).ifPresent(value -> map.put("bidi", "xxx")); + ofNullable(sectPr.getRtlGutter()).ifPresent(value -> map.put("rtlGutter", "xxx")); + stringify(sectPr.getDocGrid()).ifPresent(value -> map.put("docGrid", value)); + ofNullable(sectPr.getFormProt()).ifPresent(value -> map.put("formProt", "xxx")); + ofNullable(sectPr.getVAlign()).ifPresent(value -> map.put("vAlign", "xxx")); + ofNullable(sectPr.getNoEndnote()).ifPresent(value -> map.put("noEndnote", "xxx")); + ofNullable(sectPr.getTitlePg()).ifPresent(value -> map.put("titlePg", "xxx")); + ofNullable(sectPr.getTextDirection()).ifPresent(value -> map.put("textDirection", "xxx")); + ofNullable(sectPr.getRtlGutter()).ifPresent(value -> map.put("rtlGutter", "xxx")); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + } + + private Optional stringify(CTDocGrid ctDocGrid) { + if (ctDocGrid == null) return empty(); + var map = new TreeMap(); + ofNullable(ctDocGrid.getCharSpace()).ifPresent(value -> map.put("charSpace", String.valueOf(value))); + ofNullable(ctDocGrid.getLinePitch()).ifPresent(value -> map.put("linePitch", String.valueOf(value))); + ofNullable(ctDocGrid.getType()).ifPresent(value -> map.put("type", String.valueOf(value))); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + } + + private Optional stringify(CTRel ctRel) { + if (ctRel == null) return empty(); + var map = new TreeMap(); + ofNullable(ctRel.getId()).ifPresent(value -> map.put("id", value)); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + } + + private Optional stringify(SectPr.PgMar pgMar) { + if (pgMar == null) return empty(); + var map = new TreeMap(); + ofNullable(pgMar.getHeader()).ifPresent(value -> map.put("header", String.valueOf(value))); + ofNullable(pgMar.getFooter()).ifPresent(value -> map.put("footer", String.valueOf(value))); + ofNullable(pgMar.getGutter()).ifPresent(value -> map.put("gutter", String.valueOf(value))); + ofNullable(pgMar.getTop()).ifPresent(value -> map.put("top", String.valueOf(value))); + ofNullable(pgMar.getLeft()).ifPresent(value -> map.put("left", String.valueOf(value))); + ofNullable(pgMar.getBottom()).ifPresent(value -> map.put("bottom", String.valueOf(value))); + ofNullable(pgMar.getRight()).ifPresent(value -> map.put("right", String.valueOf(value))); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + } + + private Optional stringify(SectPr.PgSz pgSz) { + if (pgSz == null) return empty(); + var map = new TreeMap(); + ofNullable(pgSz.getOrient()).ifPresent(value -> map.put("orient", String.valueOf(value))); + ofNullable(pgSz.getW()).ifPresent(value -> map.put("w", String.valueOf(value))); + ofNullable(pgSz.getH()).ifPresent(value -> map.put("h", String.valueOf(value))); + ofNullable(pgSz.getCode()).ifPresent(value -> map.put("code", String.valueOf(value))); + return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); } } From a3f5ccaefc0e226a057d7f9127dc1dd007c9bb70 Mon Sep 17 00:00:00 2001 From: Joseph Verron Date: Sat, 9 Nov 2024 20:42:38 +0800 Subject: [PATCH 7/7] fix: simplify `stringify` method signatures Remove redundant parameters from the `stringify` methods. This change reduces complexity and potential for errors. It also improves code readability and maintainability. --- .../officestamper/test/Stringifier.java | 188 +++++++++--------- 1 file changed, 92 insertions(+), 96 deletions(-) diff --git a/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java b/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java index 5610f752..bfb33ce8 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java +++ b/engine/src/test/java/pro/verron/officestamper/test/Stringifier.java @@ -129,23 +129,32 @@ private static Predicate idEqual(BigInteger id) { } - private static String stringify( - Map map, String affectation, String delimiter, String prefix, String suffix - ) { - return map.entrySet() - .stream() - .map(e -> e.getKey() + affectation + e.getValue()) - .collect(joining(delimiter, prefix, suffix)); + /** + *

stringify.

+ * + * @param spacing a {@link PPrBase.Spacing} object + * + * @return a {@link Optional} object + * + * @since 1.6.6 + */ + private Optional stringify(PPrBase.Spacing spacing) { + if (spacing == null) return empty(); + var map = new TreeMap(); + ofNullable(spacing.getAfter()).ifPresent(value -> map.put("after", String.valueOf(value))); + ofNullable(spacing.getBefore()).ifPresent(value -> map.put("before", String.valueOf(value))); + ofNullable(spacing.getBeforeLines()).ifPresent(value -> map.put("beforeLines", String.valueOf(value))); + ofNullable(spacing.getAfterLines()).ifPresent(value -> map.put("afterLines", String.valueOf(value))); + ofNullable(spacing.getLine()).ifPresent(value -> map.put("line", String.valueOf(value))); + ofNullable(spacing.getLineRule()).ifPresent(value -> map.put("lineRule", value.value())); + return map.isEmpty() ? empty() : of(stringify(map)); } - private static Optional stringify( - List list, Function> stringify, String delimiter, String prefix, String suffix - ) { - if (list == null) return empty(); - return of(list.stream() - .map(stringify) - .flatMap(Optional::stream) - .collect(joining(delimiter, prefix, suffix))); + private static String stringify(Map map) { + return map.entrySet() + .stream() + .map(e -> "%s=%s".formatted(e.getKey(), e.getValue())) + .collect(joining(",", "{", "}")); } private String stringify(Text text) { @@ -448,22 +457,51 @@ private String stringify(List list) { /** *

stringify.

* - * @param spacing a {@link PPrBase.Spacing} object + * @param rPr a {@link RPrAbstract} object * - * @return a {@link Optional} object + * @return a {@link String} object * * @since 1.6.6 */ - private Optional stringify(PPrBase.Spacing spacing) { - if (spacing == null) return empty(); + private Optional stringify(RPrAbstract rPr) { + if (rPr == null) return empty(); var map = new TreeMap(); - ofNullable(spacing.getAfter()).ifPresent(value -> map.put("after", String.valueOf(value))); - ofNullable(spacing.getBefore()).ifPresent(value -> map.put("before", String.valueOf(value))); - ofNullable(spacing.getBeforeLines()).ifPresent(value -> map.put("beforeLines", String.valueOf(value))); - ofNullable(spacing.getAfterLines()).ifPresent(value -> map.put("afterLines", String.valueOf(value))); - ofNullable(spacing.getLine()).ifPresent(value -> map.put("line", String.valueOf(value))); - ofNullable(spacing.getLineRule()).ifPresent(value -> map.put("lineRule", value.value())); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + ofNullable(rPr.getB()).ifPresent(value -> map.put("b", String.valueOf(value.isVal()))); + ofNullable(rPr.getBdr()).ifPresent(value -> map.put("bdr", "xxx")); + ofNullable(rPr.getCaps()).ifPresent(value -> map.put("caps", String.valueOf(value.isVal()))); + ofNullable(rPr.getColor()).ifPresent(value -> map.put("color", value.getVal())); + ofNullable(rPr.getDstrike()).ifPresent(value -> map.put("dstrike", String.valueOf(value.isVal()))); + ofNullable(rPr.getI()).ifPresent(value -> map.put("i", String.valueOf(value.isVal()))); + ofNullable(rPr.getKern()).ifPresent(value -> map.put("kern", String.valueOf(value.getVal()))); + ofNullable(rPr.getLang()).ifPresent(value -> map.put("lang", value.getVal())); + stringify(rPr.getRFonts()).ifPresent(e -> map.put("rFont", e)); + ofNullable(rPr.getRPrChange()).ifPresent(value -> map.put("rPrChange", "xxx")); + ofNullable(rPr.getRStyle()).ifPresent(value -> map.put("rStyle", value.getVal())); + ofNullable(rPr.getRtl()).ifPresent(value -> map.put("rtl", String.valueOf(value.isVal()))); + ofNullable(rPr.getShadow()).ifPresent(value -> map.put("shadow", String.valueOf(value.isVal()))); + ofNullable(rPr.getShd()).ifPresent(value -> map.put("shd", value.getColor())); + ofNullable(rPr.getSmallCaps()).ifPresent(value -> map.put("smallCaps", String.valueOf(value.isVal()))); + ofNullable(rPr.getVertAlign()).ifPresent(value -> map.put("vertAlign", + value.getVal() + .value())); + ofNullable(rPr.getSpacing()).ifPresent(value -> map.put("spacing", String.valueOf(value.getVal()))); + ofNullable(rPr.getStrike()).ifPresent(value -> map.put("strike", String.valueOf(value.isVal()))); + ofNullable(rPr.getOutline()).ifPresent(value -> map.put("outline", String.valueOf(value.isVal()))); + ofNullable(rPr.getEmboss()).ifPresent(value -> map.put("emboss", String.valueOf(value.isVal()))); + ofNullable(rPr.getImprint()).ifPresent(value -> map.put("imprint", String.valueOf(value.isVal()))); + ofNullable(rPr.getNoProof()).ifPresent(value -> map.put("noProof", String.valueOf(value.isVal()))); + ofNullable(rPr.getSpecVanish()).ifPresent(value -> map.put("specVanish", String.valueOf(value.isVal()))); + ofNullable(rPr.getU()).ifPresent(value -> map.put("u", + value.getVal() + .value())); + ofNullable(rPr.getVanish()).ifPresent(value -> map.put("vanish", String.valueOf(value.isVal()))); + ofNullable(rPr.getW()).ifPresent(value -> map.put("w", String.valueOf(value.getVal()))); + ofNullable(rPr.getWebHidden()).ifPresent(value -> map.put("webHidden", String.valueOf(value.isVal()))); + ofNullable(rPr.getHighlight()).ifPresent(value -> map.put("highlight", value.getVal())); + ofNullable(rPr.getEffect()).ifPresent(value -> map.put("effect", + value.getVal() + .value())); + return map.isEmpty() ? empty() : of(stringify(map)); } /** @@ -556,56 +594,6 @@ private String stringify(R run) { .orElse(serialized); } - /** - *

stringify.

- * - * @param rPr a {@link RPrAbstract} object - * - * @return a {@link String} object - * - * @since 1.6.6 - */ - private Optional stringify(RPrAbstract rPr) { - if (rPr == null) return empty(); - var map = new TreeMap(); - ofNullable(rPr.getB()).ifPresent(value -> map.put("b", String.valueOf(value.isVal()))); - ofNullable(rPr.getBdr()).ifPresent(value -> map.put("bdr", "xxx")); - ofNullable(rPr.getCaps()).ifPresent(value -> map.put("caps", String.valueOf(value.isVal()))); - ofNullable(rPr.getColor()).ifPresent(value -> map.put("color", value.getVal())); - ofNullable(rPr.getDstrike()).ifPresent(value -> map.put("dstrike", String.valueOf(value.isVal()))); - ofNullable(rPr.getI()).ifPresent(value -> map.put("i", String.valueOf(value.isVal()))); - ofNullable(rPr.getKern()).ifPresent(value -> map.put("kern", String.valueOf(value.getVal()))); - ofNullable(rPr.getLang()).ifPresent(value -> map.put("lang", value.getVal())); - stringify(rPr.getRFonts()).ifPresent(e -> map.put("rFont", e)); - ofNullable(rPr.getRPrChange()).ifPresent(value -> map.put("rPrChange", "xxx")); - ofNullable(rPr.getRStyle()).ifPresent(value -> map.put("rStyle", value.getVal())); - ofNullable(rPr.getRtl()).ifPresent(value -> map.put("rtl", String.valueOf(value.isVal()))); - ofNullable(rPr.getShadow()).ifPresent(value -> map.put("shadow", String.valueOf(value.isVal()))); - ofNullable(rPr.getShd()).ifPresent(value -> map.put("shd", value.getColor())); - ofNullable(rPr.getSmallCaps()).ifPresent(value -> map.put("smallCaps", String.valueOf(value.isVal()))); - ofNullable(rPr.getVertAlign()).ifPresent(value -> map.put("vertAlign", - value.getVal() - .value())); - ofNullable(rPr.getSpacing()).ifPresent(value -> map.put("spacing", String.valueOf(value.getVal()))); - ofNullable(rPr.getStrike()).ifPresent(value -> map.put("strike", String.valueOf(value.isVal()))); - ofNullable(rPr.getOutline()).ifPresent(value -> map.put("outline", String.valueOf(value.isVal()))); - ofNullable(rPr.getEmboss()).ifPresent(value -> map.put("emboss", String.valueOf(value.isVal()))); - ofNullable(rPr.getImprint()).ifPresent(value -> map.put("imprint", String.valueOf(value.isVal()))); - ofNullable(rPr.getNoProof()).ifPresent(value -> map.put("noProof", String.valueOf(value.isVal()))); - ofNullable(rPr.getSpecVanish()).ifPresent(value -> map.put("specVanish", String.valueOf(value.isVal()))); - ofNullable(rPr.getU()).ifPresent(value -> map.put("u", - value.getVal() - .value())); - ofNullable(rPr.getVanish()).ifPresent(value -> map.put("vanish", String.valueOf(value.isVal()))); - ofNullable(rPr.getW()).ifPresent(value -> map.put("w", String.valueOf(value.getVal()))); - ofNullable(rPr.getWebHidden()).ifPresent(value -> map.put("webHidden", String.valueOf(value.isVal()))); - ofNullable(rPr.getHighlight()).ifPresent(value -> map.put("highlight", value.getVal())); - ofNullable(rPr.getEffect()).ifPresent(value -> map.put("effect", - value.getVal() - .value())); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); - } - private Optional stringify(RFonts rFonts) { if (rFonts == null) return empty(); var map = new TreeMap(); @@ -617,14 +605,13 @@ private Optional stringify(RFonts rFonts) { ofNullable(rFonts.getHAnsiTheme()).ifPresent(value -> map.put("hAnsiTheme", value.value())); ofNullable(rFonts.getCstheme()).ifPresent(value -> map.put("cstheme", value.value())); ofNullable(rFonts.getEastAsiaTheme()).ifPresent(value -> map.put("eastAsiaTheme", value.value())); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + return map.isEmpty() ? empty() : of(stringify(map)); } private Optional stringify(SectPr sectPr) { if (sectPr == null) return empty(); var map = new TreeMap(); - stringify(sectPr.getEGHdrFtrReferences(), this::stringify, ",", "[", "]").ifPresent(value -> map.put( - "eGHdrFtrReferences", + stringify(sectPr.getEGHdrFtrReferences(), this::stringify).ifPresent(value -> map.put("eGHdrFtrReferences", value)); stringify(sectPr.getPgSz()).ifPresent(value -> map.put("pgSz", value)); stringify(sectPr.getPgMar()).ifPresent(value -> map.put("pgMar", value)); @@ -638,23 +625,33 @@ private Optional stringify(SectPr sectPr) { ofNullable(sectPr.getTitlePg()).ifPresent(value -> map.put("titlePg", "xxx")); ofNullable(sectPr.getTextDirection()).ifPresent(value -> map.put("textDirection", "xxx")); ofNullable(sectPr.getRtlGutter()).ifPresent(value -> map.put("rtlGutter", "xxx")); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + return map.isEmpty() ? empty() : of(stringify(map)); } - private Optional stringify(CTDocGrid ctDocGrid) { - if (ctDocGrid == null) return empty(); - var map = new TreeMap(); - ofNullable(ctDocGrid.getCharSpace()).ifPresent(value -> map.put("charSpace", String.valueOf(value))); - ofNullable(ctDocGrid.getLinePitch()).ifPresent(value -> map.put("linePitch", String.valueOf(value))); - ofNullable(ctDocGrid.getType()).ifPresent(value -> map.put("type", String.valueOf(value))); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + private static Optional stringify(List list, Function> stringify) { + if (list == null) return empty(); + if (list.isEmpty()) return empty(); + return of(list.stream() + .map(stringify) + .flatMap(Optional::stream) + .collect(joining(",", "[", "]"))); } private Optional stringify(CTRel ctRel) { if (ctRel == null) return empty(); var map = new TreeMap(); ofNullable(ctRel.getId()).ifPresent(value -> map.put("id", value)); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + return map.isEmpty() ? empty() : of(stringify(map)); + } + + private Optional stringify(SectPr.PgSz pgSz) { + if (pgSz == null) return empty(); + var map = new TreeMap(); + ofNullable(pgSz.getOrient()).ifPresent(value -> map.put("orient", String.valueOf(value))); + ofNullable(pgSz.getW()).ifPresent(value -> map.put("w", String.valueOf(value))); + ofNullable(pgSz.getH()).ifPresent(value -> map.put("h", String.valueOf(value))); + ofNullable(pgSz.getCode()).ifPresent(value -> map.put("code", String.valueOf(value))); + return map.isEmpty() ? empty() : of(stringify(map)); } private Optional stringify(SectPr.PgMar pgMar) { @@ -667,16 +664,15 @@ private Optional stringify(SectPr.PgMar pgMar) { ofNullable(pgMar.getLeft()).ifPresent(value -> map.put("left", String.valueOf(value))); ofNullable(pgMar.getBottom()).ifPresent(value -> map.put("bottom", String.valueOf(value))); ofNullable(pgMar.getRight()).ifPresent(value -> map.put("right", String.valueOf(value))); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + return map.isEmpty() ? empty() : of(stringify(map)); } - private Optional stringify(SectPr.PgSz pgSz) { - if (pgSz == null) return empty(); + private Optional stringify(CTDocGrid ctDocGrid) { + if (ctDocGrid == null) return empty(); var map = new TreeMap(); - ofNullable(pgSz.getOrient()).ifPresent(value -> map.put("orient", String.valueOf(value))); - ofNullable(pgSz.getW()).ifPresent(value -> map.put("w", String.valueOf(value))); - ofNullable(pgSz.getH()).ifPresent(value -> map.put("h", String.valueOf(value))); - ofNullable(pgSz.getCode()).ifPresent(value -> map.put("code", String.valueOf(value))); - return map.isEmpty() ? empty() : of(stringify(map, "=", ",", "{", "}")); + ofNullable(ctDocGrid.getCharSpace()).ifPresent(value -> map.put("charSpace", String.valueOf(value))); + ofNullable(ctDocGrid.getLinePitch()).ifPresent(value -> map.put("linePitch", String.valueOf(value))); + ofNullable(ctDocGrid.getType()).ifPresent(value -> map.put("type", String.valueOf(value))); + return map.isEmpty() ? empty() : of(stringify(map)); } }