Giter Site home page Giter Site logo

erdos / stencil Goto Github PK

View Code? Open in Web Editor NEW
104.0 4.0 11.0 1.17 MB

templating engine for DOCX and PPTX files

Home Page: https://stencil.erdos.dev

License: Eclipse Public License 2.0

Clojure 65.81% Java 33.97% Dockerfile 0.22%
docx ooxml template-engine pptx ooxml-parser

stencil's Introduction

Stencil Template Engine

Stencil is an open source templating engine that transforms Office Open XML documents (mostly Microsoft Office's Word .docx files) from Java programs. It has a simple syntax and no programming is needed to write document templates.

stencil flow

You can use either Microsoft Word or LibreOffice to edit the document templates. The template expressions are just simple textual expressions, and you can even colour-code them to make your template more readable.

Clojars Project CI codecov contributions welcome

Hits EPL 2.0

Features

  • Works with docx and pptx files
  • Simple value substitution, conditional and repeating blocks
  • Substituting HTML text for dynamic text formatting
  • Dynamically replace images and links in the template
  • Show/hide rows and columns in tables

Getting Started with the Library

Getting Started with the Service

The project has a simple service implementation, which is available on GitHub Packages as a Container image.

Version

Latest stable version is 0.5.7

Latest snapshot version is 0.5.8-SNAPSHOT

If you are using Maven, add the followings to your pom.xml:

The dependency:

<dependency>
  <groupId>io.github.erdos</groupId>
  <artifactId>stencil-core</artifactId>
  <version>0.5.7</version>
</dependency>

And the Clojars repository:

<repository>
  <id>clojars.org</id>
  <url>https://repo.clojars.org</url>
</repository>

Alternatively, if you are using Leiningen, add the following to the :dependencies section of your project.clj file: [io.github.erdos/stencil-core "0.5.7"]

Previous versions are available on the Stencil Clojars page.

License

Copyright (c) Janos Erdos. All rights reserved. The use and distribution terms for this software are covered by the Eclipse Public License 2.0 (https://www.eclipse.org/legal/epl-2.0/) which can be found in the file LICENSE.txt at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.

stencil's People

Contributors

achempion avatar erdos avatar erdostw avatar gmile avatar mbali avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

stencil's Issues

dynamic tables - fixed-width columns

In the template I've specified fixed width for every column of a dynamic table. If the generated table has the exact number of columns as in the template, the specified width stays, but in other cases the columns are misaligned. I'd love to have a fix for this.

Could not parse template file with hyperlinks

A following exception dropped by stencil at getAllFragment call:

io.github.erdos.stencil.exceptions.ParsingException: Could not parse template file!
        at io.github.erdos.stencil.exceptions.ParsingException.wrapping(ParsingException.java:15) ~[stencil-core-0.3.3.jar:?]
        at io.github.erdos.stencil.impl.NativeTemplateFactory.prepareTemplateImpl(NativeTemplateFactory.java:98) ~[stencil-core-0.3.3.jar:?]
        at io.github.erdos.stencil.impl.NativeTemplateFactory.prepareTemplateFile(NativeTemplateFactory.java:33) ~[stencil-core-0.3.3.jar:?]
        at io.github.erdos.stencil.API.prepare(API.java:18) ~[stencil-core-0.3.3.jar:?]
        at io.github.erdos.stencil.Process.prepareTemplateFile(Process.java:129) ~[stencil-0.3.3b.jar:?]
        at hu.dbx.hammy.processor.generate.StencilProcessManager.getAllFragmentNames(StencilProcessManager.java:95) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.hammy.processor.templatehandling.tasks.PersistDataHandlerTask.addMissingRelations(PersistDataHandlerTask.java:111) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.hammy.processor.templatehandling.tasks.PersistDataHandlerTask.executeTask(PersistDataHandlerTask.java:73) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.LoggingBaseTask.run(LoggingBaseTask.java:21) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.hammy.processor.templatehandling.tasks.CreateOrUpdateTemplateVersionTask.run(CreateOrUpdateTemplateVersionTask.java:66) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.hammy.processor.templatehandling.tasks.TemplateValidatorTask.run(TemplateValidatorTask.java:293) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.hammy.tasks.SuspendMessageTask.run(SuspendMessageTask.java:57) ~[hammy-core-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.LoggingBaseTask.run(LoggingBaseTask.java:28) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.FilterItemTask.run(FilterItemTask.java:36) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.ItemManagerTask.run(ItemManagerTask.java:56) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.EventDistributorTask.run(EventDistributorTask.java:32) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChainTransactionHelper.runChainInNewTransaction(TaskChainTransactionHelper.java:11) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChainTransactionHelper$$FastClassBySpringCGLIB$$2f2e0b14.invoke(<generated>) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at hu.dbx.chaingun.util.taskchain.TaskChainTransactionHelper$$EnhancerBySpringCGLIB$$47f37b49.runChainInNewTransaction(<generated>) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.BaseExceptionHandlerTask.run(BaseExceptionHandlerTask.java:43) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.ExceptionHandlerTask.run(ExceptionHandlerTask.java:25) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.logging.MDCHelper.withContext(MDCHelper.java:39) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.tasks.LoggingMDCTask.run(LoggingMDCTask.java:25) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChain.run(TaskChain.java:114) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChainTransactionHelper.runChainInNewTransaction(TaskChainTransactionHelper.java:11) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at hu.dbx.chaingun.util.taskchain.TaskChainTransactionHelper$$FastClassBySpringCGLIB$$2f2e0b14.invoke(<generated>) ~[chaingun-3.1.0-SNAPSHOT.jar:?]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.8.RELEASE.jar:5.0.8.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE]
...
Caused by: com.ctc.wstx.exc.WstxIOException: Stream closed
        at com.ctc.wstx.sr.StreamScanner.constructFromIOE(StreamScanner.java:637) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.loadMoreFromCurrent(StreamScanner.java:1057) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.parseLocalName2(StreamScanner.java:1867) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.parseLocalName(StreamScanner.java:1827) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.handleNsAttrs(BasicStreamReader.java:3077) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.handleStartElem(BasicStreamReader.java:3028) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.nextFromTree(BasicStreamReader.java:2904) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1117) ~[woodstox-core-5.1.0.jar:5.1.0]
        at clojure.data.xml.jvm.parse$pull_seq$fn__1141.invoke(parse.clj:78) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:56) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invoke(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.data.xml.tree$seq_tree$fn__974.invoke(tree.clj:39) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.first(LazySeq.java:71) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.first(RT.java:685) ~[clojure-1.9.0.jar:?]
        at clojure.core$first__5106.invokeStatic(core.clj:55) ~[clojure-1.9.0.jar:?]
        at clojure.core$first__5106.invoke(core.clj:55) ~[clojure-1.9.0.jar:?]
        at clojure.data.xml.tree$seq_tree$fn__974$fn__982.invoke(tree.clj:46) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$filter$fn__5614.invoke(core.clj:2801) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invoke(core.clj:137) ~[clojure-1.9.0.jar:?]
        at stencil.model$parse_relation$iter__2769__2773$fn__2774.invoke(model.clj:71) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.Cons.next(Cons.java:39) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.next(RT.java:706) ~[clojure-1.9.0.jar:?]
        at clojure.core$next__5108.invokeStatic(core.clj:64) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7852.invokeStatic(protocols.clj:169) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7852.invoke(protocols.clj:124) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7807$G__7802__7816.invoke(protocols.clj:19) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7835.invokeStatic(protocols.clj:75) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7835.invoke(protocols.clj:75) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7781$G__7776__7794.invoke(protocols.clj:13) ~[clojure-1.9.0.jar:?]
        at clojure.core$reduce.invokeStatic(core.clj:6748) ~[clojure-1.9.0.jar:?]
        at clojure.core$into.invokeStatic(core.clj:6815) ~[clojure-1.9.0.jar:?]
        at clojure.core$into.invoke(core.clj:6807) ~[clojure-1.9.0.jar:?]
        at stencil.model$parse_relation.invokeStatic(model.clj:70) ~[?:?]
        at stencil.model$parse_relation.invoke(model.clj:65) ~[?:?]
        at stencil.model$load_template_model$__GT_rels__2811.invoke(model.clj:115) ~[?:?]
        at stencil.model$load_template_model.invokeStatic(model.clj:117) ~[?:?]
        at stencil.model$load_template_model.invoke(model.clj:106) ~[?:?]
        at stencil.process$prepare_template.invokeStatic(process.clj:34) ~[?:?]
        at stencil.process$prepare_template.invoke(process.clj:29) ~[?:?]
        at clojure.lang.Var.invoke(Var.java:381) ~[clojure-1.9.0.jar:?]
        at io.github.erdos.stencil.impl.NativeTemplateFactory.prepareTemplateImpl(NativeTemplateFactory.java:94) ~[stencil-core-0.3.3.jar:?]
        ... 129 more
Caused by: java.io.IOException: Stream closed
        at java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:170) ~[?:1.8.0_222]
        at java.io.BufferedInputStream.read(BufferedInputStream.java:336) ~[?:1.8.0_222]
        at com.ctc.wstx.io.BaseReader.readBytes(BaseReader.java:155) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.io.UTF8Reader.loadMore(UTF8Reader.java:369) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.io.UTF8Reader.read(UTF8Reader.java:112) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.io.ReaderSource.readInto(ReaderSource.java:89) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.io.BranchingReaderSource.readInto(BranchingReaderSource.java:57) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.loadMoreFromCurrent(StreamScanner.java:1054) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.parseLocalName2(StreamScanner.java:1867) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.StreamScanner.parseLocalName(StreamScanner.java:1827) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.handleNsAttrs(BasicStreamReader.java:3077) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.handleStartElem(BasicStreamReader.java:3028) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.nextFromTree(BasicStreamReader.java:2904) ~[woodstox-core-5.1.0.jar:5.1.0]
        at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1117) ~[woodstox-core-5.1.0.jar:5.1.0]
        at clojure.data.xml.jvm.parse$pull_seq$fn__1141.invoke(parse.clj:78) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:56) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invoke(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.data.xml.tree$seq_tree$fn__974.invoke(tree.clj:39) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.first(LazySeq.java:71) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.first(RT.java:685) ~[clojure-1.9.0.jar:?]
        at clojure.core$first__5106.invokeStatic(core.clj:55) ~[clojure-1.9.0.jar:?]
        at clojure.core$first__5106.invoke(core.clj:55) ~[clojure-1.9.0.jar:?]
        at clojure.data.xml.tree$seq_tree$fn__974$fn__982.invoke(tree.clj:46) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$filter$fn__5614.invoke(core.clj:2801) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.seq(RT.java:528) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invokeStatic(core.clj:137) ~[clojure-1.9.0.jar:?]
        at clojure.core$seq__5124.invoke(core.clj:137) ~[clojure-1.9.0.jar:?]
        at stencil.model$parse_relation$iter__2769__2773$fn__2774.invoke(model.clj:71) ~[?:?]
        at clojure.lang.LazySeq.sval(LazySeq.java:40) ~[clojure-1.9.0.jar:?]
        at clojure.lang.LazySeq.seq(LazySeq.java:49) ~[clojure-1.9.0.jar:?]
        at clojure.lang.Cons.next(Cons.java:39) ~[clojure-1.9.0.jar:?]
        at clojure.lang.RT.next(RT.java:706) ~[clojure-1.9.0.jar:?]
        at clojure.core$next__5108.invokeStatic(core.clj:64) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7852.invokeStatic(protocols.clj:169) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7852.invoke(protocols.clj:124) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7807$G__7802__7816.invoke(protocols.clj:19) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$seq_reduce.invokeStatic(protocols.clj:31) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7835.invokeStatic(protocols.clj:75) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7835.invoke(protocols.clj:75) ~[clojure-1.9.0.jar:?]
        at clojure.core.protocols$fn__7781$G__7776__7794.invoke(protocols.clj:13) ~[clojure-1.9.0.jar:?]
        at clojure.core$reduce.invokeStatic(core.clj:6748) ~[clojure-1.9.0.jar:?]
        at clojure.core$into.invokeStatic(core.clj:6815) ~[clojure-1.9.0.jar:?]
        at clojure.core$into.invoke(core.clj:6807) ~[clojure-1.9.0.jar:?]
        at stencil.model$parse_relation.invokeStatic(model.clj:70) ~[?:?]
        at stencil.model$parse_relation.invoke(model.clj:65) ~[?:?]
        at stencil.model$load_template_model$__GT_rels__2811.invoke(model.clj:115) ~[?:?]
        at stencil.model$load_template_model.invokeStatic(model.clj:117) ~[?:?]
        at stencil.model$load_template_model.invoke(model.clj:106) ~[?:?]
        at stencil.process$prepare_template.invokeStatic(process.clj:34) ~[?:?]
        at stencil.process$prepare_template.invoke(process.clj:29) ~[?:?]
        at clojure.lang.Var.invoke(Var.java:381) ~[clojure-1.9.0.jar:?]
        at io.github.erdos.stencil.impl.NativeTemplateFactory.prepareTemplateImpl(NativeTemplateFactory.java:94) ~[stencil-core-0.3.3.jar:?]
        ... 129 more

The following piece of code is able to reproduce the bug:

package hu.dbx.hammy.processor.generate;

import io.github.erdos.stencil.PreparedTemplate;
import io.github.erdos.stencil.Process;
import io.github.erdos.stencil.ProcessFactory;
import org.jodconverter.office.LocalOfficeManager;
import org.junit.Test;

import java.io.File;
import java.util.Arrays;
import java.util.Set;

public class StencilProcessManagerTest {

    @Test
    public void getAllFragmentNames() throws Exception {
        LocalOfficeManager manager = LocalOfficeManager.builder()
                .portNumbers(portArray(2002, 2002))
                .officeHome("/opt/libreoffice6.0").build();
        Process process = ProcessFactory.fromOfficeManager(manager);
        process.start();

        String fileName = "/home/developer/Downloads/x.docx";
        File file = new File(fileName);
        PreparedTemplate preparedTemplate = process.prepareTemplateFile(file);
        Set<String> allFragmentNames = preparedTemplate.getVariables().getAllFragmentNames();
        System.out.println(allFragmentNames);
    }

    private int[] portArray(int portRangeFrom, int portRangeTo) {
        int[] ports = new int[portRangeTo - portRangeFrom +1];
        Arrays.setAll(ports, i -> portRangeFrom + i);
        return ports;
    }

}

The bug seems to depend on the size of the file, but I am not sure about it.
I attached a docx file using it the bug is reproducable.
teszt.docx

Format date with locale

Currently it's possible to format a date like this

{%= date("yyyy-MMM-dd", "2020-01-23") %} // => 2020-Jan-23

It would be great to have i18n aware date format function similar to formatWithLocale

Different delimiters for number formatting

Java's Formatter function (Flags section on the page) allows using different grouping separators.

I.e. you can format numbers either 1,000.00 or 1.000,00.

Is is possible to extend format function to allow passing a locale as a third argument to be able to format numbers differently than current default option, which is 1,000.00 for {%= format("%,.2f", 1000.00) %}?

Or, instead of third argument we could have something like this {%= formatWithLocale("%,.2f", "DA", 1000.00) %}.

Another possible solution would be having more general replace function which we can apply here to solve this issue and also use it in many different contexts, something like this:

{%= replace("1-string-2", "1", "first", "2", "second") %} # => "first-string-second"

{%= replace(format("%,.2f", 1000.00), ",", ".", ".", ",") %} # => "1.000,00"

Second case is tricky though as it should replace everything in one go to be able to swap characters.

Round float decimal places in Template

Describe the bug
When the template makes any operation like multiplication for example 400,93*0.8 the result will be not rounded. It's there any way to show just 2 floating point numbers ?

Screenshots
Gleitkomma
Gleitkommaergebnis

Office versions:

  • Did you use Microsoft Office or Libre Office? Please describe the versions.
    Microsoft Office
    Environment where template is rendered:
  • Stencil version:0.3.26
  • Java version:8
  • Operating System version: Windows 10

Result of rendering a template is the template

I am trying to render the example template, but the output is identical to the template itself.
I'm using version 0.2.8 and the Purchase Reminder template.
... what am I doing wrong?

(ns scratch
  (:require [stencil.api :as s]))

(def my-data {
              "customerName" "John Doe"
              "shopName" "Example Shop"
              "items" [{"name"         "Dog food"
                        "description" "It is said to taste good."
                        "price" "$ 123"}
                       {"name" "Duct tape"
                        "description" "To fix things."
                        "price" "$ 12"}
                       {"name" "WD-40Dog food"
                        "description" "To fix other things."
                        "price" "$ 12"}],
              "total" "$ 147"             
              })


(def template-1 (s/prepare (clojure.java.io/file "templates/template.docx")))

(s/render! template-1 my-data :output "output/render1.docx")```

Different results yielded by CLI and server stencils

Describe the bug

Running stencil as a CLI tool and as a server yields different results.

To Reproduce

Stencil as a server:

  1. Download format_issue.docx into /tmp/templates (see below),

  2. Run stencil as a server:

    docker run --rm --name stencil --publish 8080:8080 --mount type=bind,source=/tmp/templates,target=/templates ghcr.io/erdos/stencil:0.3.16
  3. Make a request:

    curl localhost:8080/format_issue.docx --header 'content-type: application/json' --data '{"my_number": 10000000}'
  4. Verify the server logs, find this:

    io.github.erdos.stencil.exceptions.EvalException: java.lang.IllegalArgumentException: 
    java.util.IllegalFormatConversionException: f != java.lang.Integer
        at io.github.erdos.stencil.exceptions.EvalException.wrapping(EvalException.java:21)
        at io.github.erdos.stencil.impl.NativeEvaluator.render(NativeEvaluator.java:58)
        at io.github.erdos.stencil.API.render(API.java:53)
        at stencil.api$render_BANG_.invokeStatic(api.clj:59)
        at stencil.api$render_BANG_.doInvoke(api.clj:45)
        at clojure.lang.RestFn.invoke(RestFn.java:464)
        at stencil.service.core$_app.invokeStatic(core.clj:79)
        at stencil.service.core$_app.invoke(core.clj:71)
        at ring.middleware.json$wrap_json_body$fn__1626.invoke(json.clj:44)
        at stencil.service.core$wrap_log$fn__1676.invoke(core.clj:69)
        at stencil.service.core$wrap_err$fn__1671.invoke(core.clj:51)
        at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
    

Stencil as a CLI tool:

  1. Download format_issue.docx into /tmp/templates (see below),

  2. Create a data.json file using "Data file" contents (see below),

  3. Compile latest stencil:

  4. Run:

    java -jar /Users/eugene/oss/stencil/target/stencil-core-0.3.17-SNAPSHOT-standalone.jar /tmp/templates/format_issue.docx format_issue_data.json
    
  5. Open resulting format_issue-format_issue_data.docx file, observe the substitution to have been done correctly.

Expected behavior

Results are the consistent, e.g. either an exception is raised in both cases, or evaluation is done without an error.

Screenshots

image

Documents

format_issue.docx

Data file:

{
  "my_number": 10000000
}

Office versions:

  • We use Microsoft Office for macOS,
  • Microsoft Office version: 16.47 (21031401)

Environment where template is rendered:

  • Stencil version: stencil-core-0.3.15-SNAPSHOT-standalone
  • Java version:
    • openjdk 16.0.1 2021-04-20
    • OpenJDK Runtime Environment Homebrew (build 16.0.1+0)
    • OpenJDK 64-Bit Server VM Homebrew (build 16.0.1+0, mixed mode, sharing)
  • Operating System version: macOS 11.2.3 (20F71)

Additional context

We discovered this when debugging of why results of running stencil locally didn't match those of running stencil remotely.

Issue with numbers in json key paths

Describe the bug

I'm experiencing something that I am having a hard time explaining to myself. When referring to key.value and key.value1 paths from my json file, I don't get an output in .docx for them. Maybe I just need a second pair of eyes at what I am doing 🤔

To Reproduce

  1. Pull the code:

    $ hub clone erdos/stencil
    
  2. Pull dependencies and compile the code:

    $ lein deps
    $ lein uberjar
    
  3. Run the application, using provided files (see "Documents" section below), and open the resulting file:

    $ java -jar /Users/eugene/oss/stencil/target/stencil-core-0.3.9-standalone.jar sample.docx sample.json
    $ open sample-sample.docx
    

Expected behavior

The .docx output should look like this:

Hello from key/value!
Hello from key1/value!
Hello from key/value1!
Hello from key1/value1!

Screenshots

image

Documents

Unfortunately attaching .json files is not supported by GitHub. I attached a .zip file containing both .json and .docx: sample-json-and-docx.zip.

Office versions:

I used Microsoft Word ver. 16.40.

Environment where template is rendered:

  • Stencil version: 0.3.9
  • Java version:
    $ java --version
    openjdk 14.0.1 2020-04-14
    OpenJDK Runtime Environment (build 14.0.1+14)
    OpenJDK 64-Bit Server VM (build 14.0.1+14, mixed mode, sharing)
    
  • Operating System version: macOS 10.14.2 (18C54)

Additional context

No additional context.

Can't subtract from result of coalesce expression

The expression

{%= coalesce(1, 0) - 1 %}

fails with

Error parsing template!
Expression: {%= coalesce(1, 0) - 1 %}
Wrong tokens, unsupported arities: [1 0 {:fn "coalesce", :args 2} 1 :neg]

but {%= coalesce(1, 0) + 1 %} works and returns 2

As a temporary solution, it's possible to multiply with -1 to achieve the same result

{%= coalesce(1, 0) + 1 * -1 %}

Impossible to reference UUID fields

Describe the bug
if we have a payload like this:

{
    "u-u-i-d": 42
}

We can't reference it in a template as it gives Java.lang.NullPointerException

{%= u-u-i-d %}

Expected behavior
It would be nice to be able to reference UUID keys from the payload.

Some syntax ideas

  • {%= ['a-b'] %}
  • {%= payload['a-b'] %} (on the downside it can conflict with actual payload key from the payload)

We discussed similar issue #91 recently.

pandoc cannot parse rendered document

Pandoc works for templates but not for rendered documents:

[erdos@localhost stencil]$ pandoc --verbose  -f docx -t markdown  /tmp/tmp.nHI5VewqgZ
couldn't parse docx file

Template rendering fails on Windows

Writing the evaluated document into an OutputStream or a File on Windows causes an unexpected exception

java.util.zip.ZipException: duplicate entry: word/document.xml
 at java.util.zip.ZipOutputStream.putNextEntry (ZipOutputStream.java:232)
    stencil.process$render_writers_map.invokeStatic (process.clj:58)
    stencil.process$render_writers_map.invoke (process.clj:50)
    clojure.core$partial$fn__4759.invoke (core.clj:2515)
    io.github.erdos.stencil.EvaluatedDocument.writeToStream (EvaluatedDocument.java:30)
    io.github.erdos.stencil.EvaluatedDocument.writeToFile (EvaluatedDocument.java:25)

The problem is reproducible with the existing tests. Specifically:

lein test :only stencil.api-test/test-prepare+render+cleanup

Enabling the trace for the test duration clearly shows that the entry is indeed processed twice:

[....]
ZIP: writing "[Content_Types].xml"
ZIP: writing "_rels/.rels"
ZIP: writing "docProps/app.xml"
ZIP: writing "docProps/core.xml"
ZIP: writing "word/document.xml"
ZIP: writing "word/_rels/document.xml.rels"
ZIP: writing "word/document.xml"

lein test :only stencil.api-test/test-prepare+render+cleanup

ERROR in (test-prepare+render+cleanup) (ZipOutputStream.java:232)
Uncaught exception, not in assertion.
expected: nil
  actual: java.util.zip.ZipException: duplicate entry: word/document.xml
 at java.util.zip.ZipOutputStream.putNextEntry (ZipOutputStream.java:232)
    stencil.process$render_writers_map.invokeStatic (process.clj:59)

It's likely that this problem is caused by the Windows file separator \ (the path in the trace is already changed by FileHelper.toUnixSeparatedString)

Define local variables in the Tempalte

Is your feature request related to a problem? Please describe.
Hi erdos, it is possible to define local variables in the template?

e.g.
{% a=0%} {% a=a + service.price%} {%=a}

Best regards

`join` function to work with list of objects

We have a case when we have a list of objects (example: list of users) and would like to list them separated by a comma.

It could be solved by introducing a map function, then you could write something like this

You have bought these items: {%= join(map(order.items, "title"), ", ") %}

as described in #43.

I would like to take a simpler approach and introduce a new function join_by_attribute(list, "name", ", "), where you can pass a list of objects and attribute you want to use.

Would you accept a PR with implementation?

Performance regression with graal-based image

Describe the bug
When using graal docker image introduced in #53, .docx templates are rendered slower.

To Reproduce

  1. download & extract a .docx template file into a /tmp/templates directory: lipsum_sample.docx.zip

  2. Run two different containers, side by side (in separate Terminal windows):

    docker run --rm --name stencil-0.3.10       --publish 5000:8080 --volume /tmp/templates:/templates ghcr.io/erdos/stencil:0.3.10
    docker run --rm --name stencil-0.3.10-graal --publish 5001:8080 --volume /tmp/templates:/templates ghcr.io/erdos/stencil:0.3.10-graal
  3. Run the tests:

    • for regular container:

      $ bash -c "for test in 1 2 3 4 5; do time curl -s localhost:5000/lipsum_sample.docx --header 'content-type: application/json' --data '{}' > /tmp/lipsum_sample_regular.docx; done"
      real    0m10.385s
      user    0m0.006s
      sys     0m0.010s
      
      real    0m4.191s
      user    0m0.005s
      sys     0m0.008s
      
      real    0m4.245s
      user    0m0.005s
      sys     0m0.008s
      
      real    0m4.239s
      user    0m0.005s
      sys     0m0.008s
      
      real    0m4.263s
      user    0m0.004s
      sys     0m0.008s
      $
    • for graal container:

      $ bash -c "for test in 1 2 3 4 5; do time curl -s localhost:5001/lipsum_sample.docx --header 'content-type: application/json' --data '{}' > /tmp/lipsum_sample_graal.docx; done"
      
      real    0m19.201s
      user    0m0.005s
      sys     0m0.008s
      
      real    0m12.841s
      user    0m0.004s
      sys     0m0.010s
      
      real    0m13.111s
      user    0m0.004s
      sys     0m0.007s
      
      real    0m12.936s
      user    0m0.004s
      sys     0m0.008s
      
      real    0m13.117s
      user    0m0.004s
      sys     0m0.007s
      $
  4. Notice how graal-based image is consistently slower.

  5. Stop the containers:

    docker kill stencil-0.3.10
    docker kill stencil-0.3.10-graal

Expected behavior
Performance of application in a graal-based image is on par with that of a regular one, or faster.

However, I know very little about GraalVM, as well as the goals to build a graalvm-based image. Perhaps, performance was not among the goals for this image.

Screenshots
No screenshots.

Documents
I've attached a link to the document above. It is a 800+ pages .docx file with no template placeholders in it.

Office versions:
Not applicable as far as I can see.

Environment where template is rendered:

  • Stencil version: 0.3.10
  • Java version: I used regular and graal stencil images.
  • Operating System version: 10.14.6 (18G103)
  • Docker version: 19.03.13, build 4484c46d9d

Additional context
No additional context.

Page breaks

We output our mail merge to one word document, idea being that each new letter is on a new page. To accomplish this i have a for loop wrapped round a page break. The output always has a additional page instead of ending straight after the last document.

What is expected performance for document rendering

We have a relatively small 7-page document with tables and some if/elseif logic in it.

Calling either web-service or cli directly takes 15 seconds to render a document.

If I duplicate the content multiple times (4-8) the rendering time increases to 30 seconds.

Is it expected behaviour or performance issue?

I'm using stencil:0.3.19-graal docker image to render templates.

Updating cross-references

Is your feature request related to a problem? Please describe.

We're rendering a list of items, then using cross-references feature of Word to insert references. The rendered list looks like this:

1. Example A
2. Example B
3. Example C
  a. sub-item 1
  b. sub-item 2
  c. sub-item 3
4. Example D
5. Example F. This is example of a reference to 3.b.

The list items are based on values from input JSON. Sometimes, the input values are such that list item should not be rendered. In that case we get an output like this – item Example B was not rendered:

1. Example A
2. Example C
  a. sub-item 1
  b. sub-item 2
  c. sub-item 3
3. Example D
4. Example F. This is example of a reference to 3.b.

The reference 3.b in this case is not updated to become 2.b. Word is known to not update these automatically, except for a case when document is sent to printing.

I recorded a short video to depict the issue, and show how it's fixed manually: https://youtu.be/ST2nhi7xyBQ

Describe the solution you'd like

Once document is rendered, go through it and "refresh" all references.

Describe alternatives you've considered

None.

Additional context

None.

listing fragment names in template

feature hogy a base docx-ből, amiben benne vannak a jelölők, amik megmutatják hogy milyen néven hivatokozva, és hova kellene beszúrni a fragment docx-et, ezeket a jelölőket kiolvassa a docx-ből, és visszaadja egy listában.

EDIT: it is a request to make it possible to get a list of included fragment names for a PreparedTemplate instance.

Unable to render negative numbers

Describe the bug
When trying to render out a negative number Stencil breaks and returns nothing. It works with positive numbers and 0.

To Reproduce
Take this input { "test": -1 } and this stencil template {%= test %} and watch it break.

Expected behavior
I would expect stencil to output the negative number in the template.

Environment where template is rendered:

  • Stencil version: 0.3.26
  • Operating System version: Linux / GCP

Scaling stencil: support fetching templates from an object storage

Is your feature request related to a problem? Please describe.

We're looking at potentially scaling stencil deployment. It so happens, that right now stencil reads .docx templates from a disk on the file system. In pursue of scaling stencil deployment (e.g. have multiple copies of stencil running), we need to find a way to make sure all instances of stencil "see" the same set of .docx templates.

While I am able to think of a handful of workarounds, each with different trade-offs, it would be great for stencil to natively support fetching templates from an S3-compatible object storage.

Describe the solution you'd like

Ideally, we would like to be able to configure stencil to read templates from a Google Cloud Storage bucket. To sum up:

  • we would be able to configure a bucket name,
  • stencil application would find and read service account credentials (largely following Google Cloud guidelines),
  • finally, stencil would make an API call to (recursively) fetch all templates provided in a bucket.

While our deployment is in Google Cloud, and we would very much see support of Google Cloud Storage (a largely S3-compatible storage) and IF this proposal will gain interest, it may be a good idea to consider a code design, such that allows for other S3-compatible storages too (AWS S3, Minio, OpenStack Swift, etc.).

Describe alternatives you've considered

As a workaround, we've used an object bucket in conjunction with an utility to mount bucket as a disk volume, for stencil to consume. Unfortunately, this solution, after working fine for quite some time, proved to be limited and not scale too well with the growing amount of templates.

Another idea that comes to mind: stencil supporting template content being part of the POST request to render a template. This way templates don't have to be present on disk at all for stencil to consume. This approach hasn't been thought through, e.g.

  • would it make sense for stencil to cache the template it received in POST request's payload? if yes, when the cache should be invalidated?,
  • what would be the shape & content type of POST request's payload should be (e.g. JSON with file contents being base64-ed, a form content-type, etc.),
  • this may have network load implications, but this is surely out of scope for stencil to consider.

Additional context

None.

A map function

Is your feature request related to a problem? Please describe.

I would like to map a list of objects to a list of values derived from objects. For example, having a list of order items I would like to get a list of item prices or item titles.

Describe the solution you'd like

One solution to this problem would be to implement a function that takes a value containing a list, and a dot-separated path to value inside the object.

Given this is input JSON:

{
  "order": {
    "id": 246579,
    "items": [
      {
        "title": "Item 1",
        "price": 17.25,
      },
      {
        "title": "Item 2",
        "price": 34.25,
      },
      {
        "title": "Item 3",
        "price": 5.13,
      }
    ]
  }
}

I can see the following use-cases:

  1. Example 1:

    {%= str(map(order.items, "price")) %}
    
  2. Example 2 (given sum function exists, see #42):

    Total for order {%= order.id %} is {%= sum(map(order.items, "price")) %}
    
  3. Example 3:

    You have bought these items: {%= join(map(order.items, "title"), ", ") %}
    

Describe alternatives you've considered

Perhaps the function could be called differently, as map sounds quite generic. take? map_take?

A more powerful alternative would be based on an external dependency, e.g. such that implements a json-path spec, for example using https://github.com/gga/json-path library. Relying on json-path would allow to cover a lot more cases of arbitrary complexity. In this case the proposed function could be called query and delegate to json-path/query function provided by the library. For example:

  1. Example 1:

    {%= str(query(order, "items[*].price")) %}
    
  2. Example 2:

    Total for order {%= order.id %} is {%= sum(query(order, "items[*].price")) %}
    
  3. Example 3:

    You have bought these items: {%= join(query(order, "items[*].title"), ", ") %}
    

Additional context

-

Support dynamic templates on Stencil service

My itch...

Currently setting up a docker image, adding the template files into a static folder, reboot the server and then call an endpoint with the JSON data is quite cumbersome and definitely not for non-techies, to simply test if a stencil script is correct and using the provided data properly.

The solution I'd like

I would like to have a service where I can match up data and contracts dynamically, so I can easily preview my stencil code in a browser environment. I'd imagine a second argument to the HTTP POST that will contain the template-document. If provided, the endpoint will use the docx-file attached and return the new document as usual. Probably revolving around this piece of code in Stencil service.

curl -d '{"data":{ "firstname":"Andy"} }'
   -H "Content-Type: application/json"
   -F 'template=@/local_template.docx' http://localhost:3000/upload
   -X POST http://localhost:3000/dynamic

Alternatives I've considered

If it's easier, instead of attaching a docx as second parameter or part of the data structure, we could also post the document template as plain text, and then insert the plaintext into an empty docx-file on the server, before merging the post data with stencil.

curl -d '{"data":{ "firstname":"Andy"}, "template": "My name is {%= firstname %}" }'
   -H "Content-Type: application/json"
   -X POST http://localhost:3000/dynamic

Additional context
Here's what I'd would use this new "dynamic template feature" to:
image

Where any changes in JSON or contract template can be easily previewed (using the Office iframe viewer) and debugged 😅

I'm a strong frontender but really a horrible backender, so hoping somebody will find this feature request useful and valuable enough to pursue. 🙏

Is it possible to pass a list in stencil template?

I've tried

{%= sum((list 1 2)) %} // Could not parse!
{%= sum((1 2)) %} // Could not parse!
{%= sum((1, 2)) %} // Unexpected ',' character!
{%= sum([1, 2]) %} // Unexpected character: [

I want to have a date formatting with "th" for days, and as date format does now allow it, I'm trying to writing something like this

{% if contains(data("d", "2021-08-31"), ["1", "21", "31"]) %}th{% end %}

but for cases with 1st, 2nd, 3rd, 4th

Refreshing Table of Contents after document generation

Screen Shot 2022-01-20 at 12 59 45

Is your feature request related to a problem? Please describe.
You can create a document with multiple headings and then use a docx feature called "Table of Contents" which will display table of contents based on those headings.

In LIbreOffice it's located here: Insert -> Table of Contents and Index -> Table of Contents, Index or Bibliography...
If you change the headings, you can regenerate table of contents in with Tools -> Update -> Update all

Some headings & sections in stencil can be hidden with if clause, which means table of contents becomes outdated.

Describe the solution you'd like
It would be nice to make stencil refresh table of contents after document gets compiled.

Describe alternatives you've considered
It's possible to resolve this issue manually by introducing additional step in the rendering process, by running virtualized libreoffice and asking it to refresh the contents.

docx template can break stencil server

Currently, it's possible to create such template which will break stencil server completely. When you trigger template generation 3 times, all other templates will stop working and the stencil server will just freeze.

How to reproduce

# run stencil server
$ docker run -v `pwd`:/templates -p 8080:8080 ghcr.io/erdos/stencil:0.3.23

# start and abort template generation after 2 seconds
$ curl -X post http://localhost:8080/freezing-example-1.docx -d '{}' --header 'Content-Type: application/json'

Possible cause

In docx document it's possible to include other docx files with special template markup, I think in freezing-example-1.docx this is what can make stencil server freeze (we try to use invalid template path).

In second example freezing-example-2.docx I reconverted file odt -> docx which excluded this type of special markup but it still can freeze the stencil service.

freezing-example-1.docx
freezing-example-2.docx

Cross-references: "Reference source not found" error

Describe the bug
When rendering a template that contains cross-references, a cross-reference is replaced with an error text:

Error; Reference source not found.

To Reproduce

  1. Download "cross_reference_issue_demo.docx" template document (see below),

  2. Create simple data file:

    {
      "my_value": "Hello, world!"
    }
  3. Compile latest stencil:

    lein uberjar
  4. Run (change path to point to compiled stencil jar):

    java -jar /Users/eugene/oss/stencil/target/stencil-core-0.3.15-SNAPSHOT-standalone.jar Cross_reference.docx data.json;
  5. Open resulting file.

Expected behavior
Cross-references are rendered without an error message.

Screenshots
image

Documents

Office versions:

  • We use Microsoft Office for macOS,
  • Microsoft Office version: 16.47 (21031401)

Environment where template is rendered:

  • Stencil version: stencil-core-0.3.15-SNAPSHOT-standalone
  • Java version:
    • openjdk 15.0.2 2021-01-19
    • OpenJDK Runtime Environment (build 15.0.2+7)
    • OpenJDK 64-Bit Server VM (build 15.0.2+7, mixed mode, sharing)
  • Operating System version: macOS 11.2.3 (20D91)

Additional context
If we remove the dynamic value from template, the resulting document will not contain errors, e.g. cross-references are rendered correctly.

100% CPU consumption when running in docker container

Describe the bug
The service application, when ran from within a container, consumer 100% of CPU.

To Reproduce
Run the container:

docker run --name stencil_perf_issue ghcr.io/erdos/stencil:latest

Open "Activity Monitor" and note high CPU consumption:

image

Alternatively, check the output of ctop utility:

image

image

Expected behavior

The application does not consume 100% of CPU when idle.

Screenshots
(see above)

Documents
No.

Office versions:
No.

Environment where template is rendered:

  • Stencil version: 0.3.10-SNAPSHOT
  • Java version: OpenJDK 8 (as of this writing; one used in docker image)
  • Operating System version: Debian Buster (not sure of exact version of linux using by Docker though, but can dig that up)

Additional context
Running the server application directly on my host machine does not yield the same issue.

cd service
lein uberjar
STENCIL_HTTP_PORT=5000 STENCIL_TEMPLATE_DIR=/tmp/stencil-templates java -jar /Users/eugene/oss/stencil/service/target/stencil-service-0.3.10-SNAPSHOT-standalone.jar

In my macOS host I have:

  • Stencil version: 0.3.10-SNAPSHOT

  • Java version:

    openjdk 14.0.1 2020-04-14
    OpenJDK Runtime Environment (build 14.0.1+14)
    OpenJDK 64-Bit Server VM (build 14.0.1+14, mixed mode, sharing)
    
  • Operating System version: macOS 10.14.6 (18G103)

Noting this as possibly, this may be an issue related to runtime environment.

ways to embed dynamic images

make it possible to insert images dynamically.

Considerations

  1. image content should be supplied as a base64 encoded string
  2. make it possible to replace existing images (so we will not have to deal with positioning)

A sum function

Is your feature request related to a problem? Please describe.

I'm working on a template that has the need to sum values from a list. For example, this is a list I have:

{
  "values": [4, 19, 13.2, 55, 1]
}

It would be useful to be able to sum the values and get a value of 92.2 in return, for example.

Describe the solution you'd like

One solution to this problem would be to implement a function that takes a list, and returns sum of values:

Given the following is an input JSON data:

{
  "values": [4, 19, 13.2, 55, 1]
}

The function could be used like this:

  • Example 1:

    {%= sum(values) %}
    
  • Example 2:

    {%= sum(values) / length(values) %}
    
  • Example 3:

    {% if sum(values) > 10 %}
      Looks like the sum is greater than 10!
    {% end %}
    

Describe alternatives you've considered

I can't think of any at the moment.

Additional context

I cannot add any more context at the moment.

Publish a docker image

Is your feature request related to a problem? Please describe.

Right now I re-build docker image manually & push it to a private repository every time the code gets updated. Having docker image built & pushed to Github Container Registry automatically as part of CI would save me a lot of time.

Describe the solution you'd like

One way to fix the issue would be to modify GitHub Actions config to automatically build & push a stencil:latest image on every commit to master. Perhaps it would make sense to create a versioned image on each tag too, but having stencil:latest would be good enough for starters.

Describe alternatives you've considered

Nothing.

Additional context

Nothing.

Dividing by dynamic value doesn't work

Describe the bug

{%= one / two %}

raises an exception

Error rendering template!
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
- java.lang.ArithmeticException Non-terminating decimal expansion; no exact representable decimal result.

for the payload

{
  "one": 4.3,
  "two": 3.0
}

It seems like like the partition part of division is causing the problem here. {%= one / 2 %} works fine but it breaks other way around.

Environment where template is rendered:

  • Stencil version: 0.4.1
  • Operating System version: Linux Alpine

function to insert page break

Currently, we need to insert a real page break in the template if we want to have one. But if it is inside a contros structure, this scatters the template and makes it more difficult to follow.

Proposal: Lets create a function that insrets a page break. Something like: {%=pageBreak()%}!

Newline character support

Describe the bug
If I have a json payload

{
  "value": "One\nTwo\n\nThree\r\nFour\\nFive"
}

and a stencil template

{%= value %}

it gets compiled to this string

One Two  Three Four\nFive

Expected behavior
It would be nice to have \n character render new lines

Office versions:
LibreOffice 7.1.5.2

Environment where template is rendered:
Docker image: ghcr.io/erdos/stencil:0.3.31

`coalesce` function raises an error

Describe the bug
coalesce function raises an error

To Reproduce

  1. create a document test.docx
{%= coalesce(value1, 0) %}
  1. create a payload.json
{
  "value1": 42
}
  1. run the container
$  docker run -v (pwd):/templates -p 8080:8080 ghcr.io/erdos/stencil:0.3.15-graal
  1. compose the document
$  curl -XPOST localhost:8080/test.docx --header "Content-Type: application/json" --data @payload.json > rendered.docx
  1. get the exception
java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Integer

Expected behavior
A document with "42" being composed

Office versions:

  • Word Document created using apple pages version 11.1 (7031.0.102), same exception when creating in MS Word

Environment where template is rendered:
Docker image from ghcr.io/erdos/stencil:0.3.15-graal

`map` function doesn't work

Describe the bug

{%= map("name", items) %}

raises an exception

Error rendering template!
clojure.lang.ExceptionInfo: Wrong data, expected map, got:  {:data {"price" 10M, "name" "Wood"}}
- clojure.lang.ExceptionInfo Wrong data, expected map, got: 

given the payload

{
  "items": [
    {
      "price": 10,
      "name": "Wood"
    },
    {
      "price": "20",
      "name": "Stone"
    }
  ]
}

Office versions:

  • LibreOffice 7.2.2.2

Environment where template is rendered:

  • Stencil version: 0.4.1-SNAPSHOT
  • Java version:
    openjdk version "1.8.0_302"
    OpenJDK Runtime Environment (IcedTea 3.20.0) (Alpine 8.302.08-r2)
    OpenJDK 64-Bit Server VM (build 25.302-b08, mixed mode)
  • Operating System version: Linux Alpine

Improve visibility into template rendering process

Is your feature request related to a problem? Please describe.

Trying to find an issue when rendering a many-pages long template with many variables can become really hard. One things that could help pinpointing problematic place in the document would be to improve visibility into what values variables are evaluated into.

Describe the solution you'd like

It would be nice to be able to supply a {"debug": true} / {"trace": true} in an HTTP request payload (or in a header) to make Stencil log extra information about template processing.

There's this flag in the code, which, when changed to true – outputs value of variable.

It's quite handy, but, when enabled, it would work for all requests which can make the log look pretty noisy during a heavy load.

One caveat with this is given two concurrent requests has the {"debug": true} / {"trace": true} in them, it can become hard to see which log lines belong to which request. Perhaps, logging request_id together with a template name would improve this.

Describe alternatives you've considered

Additional context

All text disappears

I tried to evaulate the attached docx, but all text disappears in the generated PDF. It seems, that without the dynamic param, it works, text does not dissapear.
autos_assistance_kartya.docx (erdos: removed sensitive file)

parse errors should give context information

When the document template is invalid then the parsing exception should contain information about the failing expression. It would make easier to find and fix the failing expression in the template document.

consideratations

  • we should include the original expression so it is easier to find and fix in in the template file.
  • we should give a hint on how to fix it.
  • maybe show the context (preceding/succeeding n characters) in the error message too?

examples

  • error messages on unparseable if or for or {%=...%} expressions should contain the text of the original expression,
  • unexpected {%else%} calls (more than 1) should show the text of the original if expression.
  • unexpected {%end%} calls (too many): what should we show?

add functions to service on-the-fly

Introduction:

A quick analysis of recent issues shows two patterns:

  1. Most recent issues were about introducing new functions to the core stencil template language. 

While these show a reasonable demand for a sane language core, it also points out that extending stencil functionality is not straightforward.

  2. Lot of issues are about running stencil as a service. 

When starting the project, the focus was on the Java library and the service was created as an example to show how to utilize it. Over time, the service has been used in production in many places, so more effort should be expended on the support of the app.

It should be made easier to extend stencil functionality in the containerised app.

Proposal:

If stencil finds a stencil.js file in the template directory, it loads it and any functions defined in the javascript file will be made available during template evaluation.

The Java Nashorn scripting engine can be used for running the scripts. The file should be reloaded when the js file changes.

This should enable developers to add new functions to the stencil service without recompiling/rebuilding the app.

Calculating a range between dates

Is your feature request related to a problem? Please describe.
I have two dates in a payload, it would be nice to be able to calculate a range between them in a stencil template.

Other similar usecase: subtract a year from a given date

Describe the solution you'd like
I'm thinking about a function to convert dates to timestamps, do the subtraction and then formatting timestamp seconds either with a date() function or just with simple math by dividing timestamp seconds to days / hours / etc.

Accessing elements in list by index and range

You can have this data structure in json

{"test": [1, 2, 3]}

In order to have it formatted like 1, 2 and 3 you have to use something like this

{%= join(test[0..count(test)], ", ") %} and {%= test[count(test) - 1], ", ") %}
// or like this
{%= join(test[0..-2], ", ") %} and {%= test[-1], ", ") %}

It whould be great to have [] operator for accessing elements and count and count them.

EvaluatedDocument.toInputStream usage may result in corrupt documents

The EvaluatedDocument.toInputStream function's default implementation uses a PipedInputStream/PipedOutputStream pair, and writes into the output stream in an executor thread. If this write results in an error, the thread reading the input stream will not get notified of the problem, and reads an unfinished and probably corrupt document.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.