word template automatic generation engine written in JAVA

Hello, I'm TJ

An inspirational programmer who recommends 10000 open source projects and tools

What is TJ's biggest headache when he works on the project? Of course, it is necessary to write all kinds of documents, especially for projects in large companies. Although a mature project management process really depends on all kinds of documents to clarify project milestones, specific design confirmation and requirements Division, TJ still prefers to spend time developing code.

In particular, the formats of some documents are similar. Can our programs play their strengths and use programs to generate and output the specified word documents, so as to reduce their handwriting time?

Of course!

Today, TJ Jun will share with you a special word template engine, POI TL (POI template language). Based on Apache Poi, this engine can directly generate the corresponding word documents according to the contents entered by users, which is very convenient.

Apache Poi is a free and open source cross platform Java API written in Java. The API can read and write office format documents through Java programs. It can be said that it is the best office processing library among the Java libraries at this stage. It may not need to add a word or two. So POI TL based on Apache Poi allows you to do whatever you want anywhere in a word document.

For example, if you want to generate a document named TJ Jun awesome. docx and include the text {title}} in the document, you only need one sentence of code, which is also the core of the whole engine:

//The core API uses a minimalist design and requires only one line of code
XWPFTemplate.compile("TJ You're great.docx").render(new HashMap<String, Object>(){{
        put("title", "Poi-tl template engine");
}}).writeToFile("out_TJ You're great.docx");

The overall design of POI TL adopts template + data model = output mode

Configure provides template configuration functions, such as syntax configuration and plug-in configuration:

/**
 * Plug in configuration
 * 
 * @author Sayi
 * @version 1.0.0
 */
public class Configure {

    // defalut expression
    private static final String DEFAULT_GRAMER_REGEX = "[\\w\\u4e00-\\u9fa5]+(\\.[\\w\\u4e00-\\u9fa5]+)*";

    // Highest priority
    private Map<String, RenderPolicy> customPolicys = new HashMap<String, RenderPolicy>();
    // Low priority
    private Map<Character, RenderPolicy> defaultPolicys = new HashMap<Character, RenderPolicy>();

    /**
     * Reference rendering policy
     */
    private List<ReferenceRenderPolicy<?>> referencePolicies = new ArrayList<>();

    /**
     * Syntax prefix
     */
    private String gramerPrefix = "{{";
    /**
     * Syntax suffix
     */
    private String gramerSuffix = "}}";

    /**
     * By default, it supports the regularization of Chinese, letters, numbers and underscores
     */
    private String grammerRegex = DEFAULT_GRAMER_REGEX;

    /**
     * Template expression mode, the default is POI_TL_MODE
     */
    private ELMode elMode = ELMode.POI_TL_STANDARD_MODE;

    /**
     * Processing strategy when rendering data verification fails
     * <ul>
     * <li>DiscardHandler: Do nothing</li>
     * <li>ClearHandler: Empty label</li>
     * <li>AbortHandler: Throw exception</li>
     * </ul>
     */
    private ValidErrorHandler handler = new ClearHandler();

    private Configure() {
        plugin(GramerSymbol.TEXT, new TextRenderPolicy());
        plugin(GramerSymbol.IMAGE, new PictureRenderPolicy());
        plugin(GramerSymbol.TABLE, new MiniTableRenderPolicy());
        plugin(GramerSymbol.NUMBERIC, new NumbericRenderPolicy());
        plugin(GramerSymbol.DOCX_TEMPLATE, new DocxRenderPolicy());
    }

    /**
     * Create default configuration
     * 
     * @return
     */
    public static Configure createDefault() {
        return newBuilder().build();
    }

    /**
     * Builder
     * 
     * @return
     */
    public static ConfigureBuilder newBuilder() {
        return new ConfigureBuilder();
    }

    /**
     * Add or change syntax plug-ins
     * 
     * @param c
     *            grammar
     * @param policy
     *            strategy
     */
    public Configure plugin(char c, RenderPolicy policy) {
        defaultPolicys.put(Character.valueOf(c), policy);
        return this;
    }

    /**
     * Add or change syntax plug-ins
     * 
     * @param symbol
     *            grammar
     * @param policy
     *            strategy
     * @return
     */
    Configure plugin(GramerSymbol symbol, RenderPolicy policy) {
        defaultPolicys.put(symbol.getSymbol(), policy);
        return this;
    }

    /**
     * Customize templates and policies
     * 
     * @param tagName
     *            Template name
     * @param policy
     *            strategy
     */
    public void customPolicy(String tagName, RenderPolicy policy) {
        customPolicys.put(tagName, policy);
    }

    /**
     * New reference rendering policy
     * 
     * @param policy
     */
    public void referencePolicy(ReferenceRenderPolicy<?> policy) {
        referencePolicies.add(policy);
    }

    /**
     * Get label policy
     * 
     * @param tagName
     *            Template name
     * @param sign
     *            grammar
     */
    // Query Operations

    public RenderPolicy getPolicy(String tagName, Character sign) {
        RenderPolicy policy = getCustomPolicy(tagName);
        return null == policy ? getDefaultPolicy(sign) : policy;
    }

    public List<ReferenceRenderPolicy<?>> getReferencePolicies() {
        return referencePolicies;
    }
    
    private RenderPolicy getCustomPolicy(String tagName) {
        return customPolicys.get(tagName);
    }

    private RenderPolicy getDefaultPolicy(Character sign) {
        return defaultPolicys.get(sign);
    }

    public Map<Character, RenderPolicy> getDefaultPolicys() {
        return defaultPolicys;
    }

    public Map<String, RenderPolicy> getCustomPolicys() {
        return customPolicys;
    }

    public Set<Character> getGramerChars() {
        return defaultPolicys.keySet();
    }

    public String getGramerPrefix() {
        return gramerPrefix;
    }

    public String getGramerSuffix() {
        return gramerSuffix;
    }

    public String getGrammerRegex() {
        return grammerRegex;
    }

    public ELMode getElMode() {
        return elMode;
    }

    public ValidErrorHandler getValidErrorHandler() {
        return handler;
    }

    public static class ConfigureBuilder {
        private boolean regexForAll;
        private Configure config;

        public ConfigureBuilder() {
            config = new Configure();
        }

        public ConfigureBuilder buildGramer(String prefix, String suffix) {
            config.gramerPrefix = prefix;
            config.gramerSuffix = suffix;
            return this;
        }

        public ConfigureBuilder buildGrammerRegex(String reg) {
            config.grammerRegex = reg;
            return this;
        }

        public ConfigureBuilder supportGrammerRegexForAll() {
            this.regexForAll = true;
            return this;
        }

        public ConfigureBuilder setElMode(ELMode mode) {
            config.elMode = mode;
            return this;
        }

        public ConfigureBuilder setValidErrorHandler(ValidErrorHandler handler) {
            config.handler = handler;
            return this;
        }

        public ConfigureBuilder addPlugin(char c, RenderPolicy policy) {
            config.plugin(c, policy);
            return this;
        }

        public ConfigureBuilder customPolicy(String tagName, RenderPolicy policy) {
            config.customPolicy(tagName, policy);
            return this;
        }

        public ConfigureBuilder referencePolicy(ReferenceRenderPolicy<?> policy) {
            config.referencePolicy(policy);
            return this;
        }

        public ConfigureBuilder bind(String tagName, RenderPolicy policy) {
            config.customPolicy(tagName, policy);
            return this;
        }

        public Configure build() {
            if (config.elMode == ELMode.SPEL_MODE) {
                regexForAll = true;
            }
            if (regexForAll) {
                config.grammerRegex = RegexUtils.createGeneral(config.gramerPrefix,
                        config.gramerSuffix);
            }
            return config;
        }
    }
}

Visitor provides template parsing:

/**
 * Template parser
 * 
 * @author Sayi
 * @version 1.4.0
 */
public class TemplateVisitor implements Visitor {
    private static Logger logger = LoggerFactory.getLogger(TemplateVisitor.class);

    private Configure config;
    private List<ElementTemplate> eleTemplates;

    private Pattern templatePattern;
    private Pattern gramerPattern;

    static final String FORMAT_TEMPLATE = "{0}{1}{2}{3}";
    static final String FORMAT_GRAMER = "({0})|({1})";

    public TemplateVisitor(Configure config) {
        this.config = config;
        initPattern();
    }

    @Override
    public List<ElementTemplate> visitDocument(XWPFDocument doc) {
        if (null == doc) return null;
        this.eleTemplates = new ArrayList<ElementTemplate>();
        logger.info("Visit the document start...");
        visitParagraphs(doc.getParagraphs());
        visitTables(doc.getTables());
        visitHeaders(doc.getHeaderList());
        visitFooters(doc.getFooterList());
        logger.info("Visit the document end, resolve and create {} ElementTemplates.",
                this.eleTemplates.size());
        return eleTemplates;
    }

    void visitHeaders(List<XWPFHeader> headers) {
        if (null == headers) return;
        for (XWPFHeader header : headers) {
            visitParagraphs(header.getParagraphs());
            visitTables(header.getTables());
        }
    }

    void visitFooters(List<XWPFFooter> footers) {
        if (null == footers) return;
        for (XWPFFooter footer : footers) {
            visitParagraphs(footer.getParagraphs());
            visitTables(footer.getTables());
        }
    }

    void visitParagraphs(List<XWPFParagraph> paragraphs) {
        if (null == paragraphs) return;
        for (XWPFParagraph paragraph : paragraphs) {
            visitParagraph(paragraph);
        }
    }

    void visitTables(List<XWPFTable> tables) {
        if (null == tables) return;
        for (XWPFTable tb : tables) {
            visitTable(tb);
        }
    }

    void visitTable(XWPFTable table) {
        if (null == table) return;
        List<XWPFTableRow> rows = table.getRows();
        if (null == rows) return;
        for (XWPFTableRow row : rows) {
            List<XWPFTableCell> cells = row.getTableCells();
            if (null == cells) continue;
            for (XWPFTableCell cell : cells) {
                visitParagraphs(cell.getParagraphs());
                visitTables(cell.getTables());
            }
        }
    }

    void visitParagraph(XWPFParagraph paragraph) {
        if (null == paragraph) return;
        RunningRunParagraph runningRun = new RunningRunParagraph(paragraph, templatePattern);
        List<XWPFRun> refactorRun = runningRun.refactorRun();
        if (null == refactorRun) return;
        for (XWPFRun run : refactorRun) {
            visitRun(run);
        }
    }

    void visitRun(XWPFRun run) {
        String text = null;
        if (null == run || StringUtils.isBlank(text = run.getText(0))) return;
        ElementTemplate elementTemplate = parseTemplateFactory(text, run);
        if (null != elementTemplate) eleTemplates.add(elementTemplate);
    }

    private <T> ElementTemplate parseTemplateFactory(String text, T obj) {
        logger.debug("Resolve where text: {}, and create ElementTemplate", text);
        // temp ,future need to word analyze
        if (templatePattern.matcher(text).matches()) {
            String tag = gramerPattern.matcher(text).replaceAll("").trim();
            if (obj.getClass() == XWPFRun.class) {
                return TemplateFactory.createRunTemplate(tag, config, (XWPFRun) obj);
            } else if (obj.getClass() == XWPFTableCell.class)
                // return CellTemplate.create(symbol, tagName, (XWPFTableCell)
                // obj);
                return null;
        }
        return null;
    }

    private void initPattern() {
        String signRegex = getGramarRegex(config);
        String prefixRegex = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());
        String suffixRegex = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());

        templatePattern = Pattern.compile(MessageFormat.format(FORMAT_TEMPLATE, prefixRegex,
                signRegex, config.getGrammerRegex(), suffixRegex));
        gramerPattern = Pattern
                .compile(MessageFormat.format(FORMAT_GRAMER, prefixRegex, suffixRegex));
    }

    private String getGramarRegex(Configure config) {
        List<Character> gramerChar = new ArrayList<Character>(config.getGramerChars());
        StringBuilder reg = new StringBuilder("(");
        for (int i = 0;; i++) {
            Character chara = gramerChar.get(i);
            String escapeExprSpecialWord = RegexUtils.escapeExprSpecialWord(chara.toString());
            if (i == gramerChar.size() - 1) {
                reg.append(escapeExprSpecialWord).append(")?");
                break;
            } else reg.append(escapeExprSpecialWord).append("|");
        }
        return reg.toString();
    }

}

Finally, RenderPolicy is the extension point of rendering policy. The Render module provides the extension point of RenderDataCompute expression calculation, and renders each label through RenderPolicy.

Of course, if you want to use POI TL well, you still need to spend some time to study the syntax of specific modules. Fortunately, POI TL provides detailed explanation of example code, which can be mastered quickly as long as you study it carefully

Can we let our friends reduce the pressure of writing documents? Remember to give TJ feedback after using it! The full project address of the partner you want to use is here:

Click on the bottom card to pay attention to the official account "TJ Jun".

Reply to "generate word" to obtain the warehouse address

Pay attention to me and know a cow x, easy-to-use and interesting thing every day

Posted by Vince on Tue, 09 Nov 2021 23:40:31 -0800