diff --git a/engine/src/main/java/pro/verron/officestamper/api/OfficeStamperConfiguration.java b/engine/src/main/java/pro/verron/officestamper/api/OfficeStamperConfiguration.java index b3b2dc31..e2a7c05f 100644 --- a/engine/src/main/java/pro/verron/officestamper/api/OfficeStamperConfiguration.java +++ b/engine/src/main/java/pro/verron/officestamper/api/OfficeStamperConfiguration.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; /** @@ -25,8 +26,7 @@ public interface OfficeStamperConfiguration { * allows you to define how unresolved expressions are handled in a more flexible and * comprehensive manner. */ - @Deprecated(since = "2.5", forRemoval = true) - boolean isFailOnUnresolvedExpression(); + @Deprecated(since = "2.5", forRemoval = true) boolean isFailOnUnresolvedExpression(); /** * Sets the failOnUnresolvedExpression flag to determine whether unresolved expressions should @@ -113,8 +113,7 @@ public interface OfficeStamperConfiguration { * allows you to define how unresolved expressions are handled in a more flexible and * comprehensive manner. */ - @Deprecated(since = "2.5", forRemoval = true) - OfficeStamperConfiguration replaceUnresolvedExpressions( + @Deprecated(since = "2.5", forRemoval = true) OfficeStamperConfiguration replaceUnresolvedExpressions( boolean replaceUnresolvedExpressions ); @@ -275,14 +274,13 @@ OfficeStamperConfiguration setSpelParserConfiguration( List customFunctions(); + void addCustomFunction(String name, Supplier implementation); + NeedsFunctionImpl addCustomFunction(String name, Class class0); NeedsBiFunctionImpl addCustomFunction(String name, Class class0, Class class1); NeedsTriFunctionImpl addCustomFunction( - String name, - Class class0, - Class class1, - Class class2 + String name, Class class0, Class class1, Class class2 ); } diff --git a/engine/src/main/java/pro/verron/officestamper/core/DocxStamperConfiguration.java b/engine/src/main/java/pro/verron/officestamper/core/DocxStamperConfiguration.java index f2c7bbf8..8b3944bd 100644 --- a/engine/src/main/java/pro/verron/officestamper/core/DocxStamperConfiguration.java +++ b/engine/src/main/java/pro/verron/officestamper/core/DocxStamperConfiguration.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; /** * The {@link DocxStamperConfiguration} class represents the configuration for @@ -403,6 +404,11 @@ public void addCustomFunction(CustomFunction function) { } + @Override public void addCustomFunction(String name, Supplier implementation) { + this.addCustomFunction(new CustomFunction(name, List.of(), args -> implementation.get())); + } + + @Override public NeedsFunctionImpl addCustomFunction(String name, Class class0) { return new FunctionBuilder<>(this, name, class0); } diff --git a/engine/src/test/java/pro/verron/officestamper/test/Contexts.java b/engine/src/test/java/pro/verron/officestamper/test/Contexts.java index c2b78a6e..c078a701 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/Contexts.java +++ b/engine/src/test/java/pro/verron/officestamper/test/Contexts.java @@ -7,6 +7,7 @@ import pro.verron.officestamper.preset.StampTable; import java.nio.file.Path; +import java.time.LocalDate; import java.util.*; import static org.junit.jupiter.params.provider.Arguments.of; @@ -192,18 +193,6 @@ public static Map coupleContext() { return context; } - /** - *

nowContext.

- * - * @return a {@link Contexts.DateContext} object - * - * @since 1.6.6 - */ - public static DateContext nowContext() { - var now = new Date(); - return new DateContext(now); - } - /** *

mapAndReflectiveContext.

* @@ -285,13 +274,6 @@ public record Role(String name, String actor) {} */ public record Characters(List characters) {} - /** - * Represents a Date context. - * - * @param date The Date value to be encapsulated in the context. - */ - public record DateContext(Date date) {} - public record ZonedDateContext(java.time.ZonedDateTime date) {} /** @@ -614,4 +596,8 @@ public record Name(String name) {} public static class EmptyContext {} public record TableContext(StampTable characters) {} + + public record DateContext(LocalDate date) { + + } } diff --git a/engine/src/test/java/pro/verron/officestamper/test/CustomFunctionTests.java b/engine/src/test/java/pro/verron/officestamper/test/CustomFunctionTests.java index 1951b5bc..d2ce6eb4 100644 --- a/engine/src/test/java/pro/verron/officestamper/test/CustomFunctionTests.java +++ b/engine/src/test/java/pro/verron/officestamper/test/CustomFunctionTests.java @@ -1,24 +1,31 @@ package pro.verron.officestamper.test; +import org.docx4j.openpackaging.exceptions.Docx4JException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import pro.verron.officestamper.test.Functions.UppercaseFunction; +import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Path; +import java.time.LocalDate; +import java.time.Month; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; import static org.junit.jupiter.api.Assertions.assertEquals; import static pro.verron.officestamper.preset.OfficeStamperConfigurations.standard; import static pro.verron.officestamper.test.TestUtils.getResource; +import static pro.verron.officestamper.test.TestUtils.makeResource; -@DisplayName("Custom functions") -class CustomFunctionTests { +@DisplayName("Custom functions") class CustomFunctionTests { - @DisplayName("Should works with variables, multiline text, in comment content, inside comment, and in repetitions.") - @Test() - void features() { - var config = standard() - .exposeInterfaceToExpressionLanguage(UppercaseFunction.class, Functions.upperCase()); + @DisplayName("Should allow to inject full interfaces") @Test() void interfaces() { + var config = standard().exposeInterfaceToExpressionLanguage(UppercaseFunction.class, Functions.upperCase()); var template = getResource(Path.of("CustomExpressionFunction.docx")); var context = Contexts.show(); var stamper = new TestDocxStamper<>(config); @@ -60,4 +67,66 @@ void features() { var actual = stamper.stampAndLoadAndExtract(template, context); assertEquals(expected, actual); } + + @DisplayName("Should allow to inject lambda functions") @Test() void functions() + throws IOException, Docx4JException { + var config = standard(); + config.addCustomFunction("toUppercase", String.class) + .withImplementation(String::toUpperCase); + var template = makeResource("${toUppercase(name)}"); + var context = Contexts.show(); + var stamper = new TestDocxStamper<>(config); + var expected = """ + THE SIMPSONS + """; + var actual = stamper.stampAndLoadAndExtract(template, context); + assertEquals(expected, actual); + } + + @DisplayName("Should allow to inject lambda suppliers.") @Test() void suppliers() + throws IOException, Docx4JException { + var config = standard(); + config.addCustomFunction("foo", () -> List.of("a", "b", "c")); + var template = makeResource("${foo()}"); + var context = Contexts.empty(); + var stamper = new TestDocxStamper<>(config); + var expected = """ + [a, b, c] + """; + var actual = stamper.stampAndLoadAndExtract(template, context); + assertEquals(expected, actual); + } + + @DisplayName("Should allow to inject lambda suppliers.") @Test() void bifunctions() + throws IOException, Docx4JException { + var config = standard(); + config.addCustomFunction("Add", String.class, Integer.class) + .withImplementation((s, i) -> new BigDecimal(s).add(new BigDecimal(i))); + var template = makeResource("${Add('3.22', 4)}"); + var context = Contexts.empty(); + var stamper = new TestDocxStamper<>(config); + var expected = """ + 7.22 + """; + var actual = stamper.stampAndLoadAndExtract(template, context); + assertEquals(expected, actual); + } + + @DisplayName("Should allow to inject lambda trifunctions.") + @CsvSource({"ZH,2024 四月", "FR,2024 avril", "EN,2024 April", "JA,2024 4月", "HE,2024 אפריל", "IT,2024 aprile"}) + @ParameterizedTest() void trifunctions(String tag, String expected) + throws IOException, Docx4JException { + var config = standard(); + config.addCustomFunction("format", LocalDate.class, String.class, String.class) + .withImplementation((date, pattern, languageTag) -> { + var locale = Locale.forLanguageTag(languageTag); + var formatter = DateTimeFormatter.ofPattern(pattern, locale); + return formatter.format(date); + }); + var template = makeResource("${format(date,'yyyy MMMM','%s')}" .formatted(tag)); + var context = new Contexts.DateContext(LocalDate.of(2024, Month.APRIL, 1)); + var stamper = new TestDocxStamper<>(config); + var actual = stamper.stampAndLoadAndExtract(template, context); + assertEquals(expected + "\n", actual); + } }