查看原文
其他

用JAVA写的word模板自动生成引擎

TJ TJ君 2021-12-11

大家好,我是TJ

一个励志推荐10000款开源项目与工具的程序员


TJ君做项目的时候最头疼什么?当然是写各种文档啦,尤其是在大公司做项目,各种规范文档不可少,虽然说一个成熟的项目管理过程中的确是要依靠各种文档来明确项目里程碑及具体的设计确认和需求分工,但是TJ君还是更喜欢把时间花在开发代码上。

尤其是有些文档的格式都差不多,那是不是我们程序猿可以发挥特长,用程序来生成输出指定的word文档,减少自己的手写时间呢?

当然是可以的!

今天TJ君就要给大家分享一款Word专用的模板引擎,Poi-tl(Poi-template-language)。这款引擎基于Apache Poi,可以根据用户输入的内容直接生成相应的word文档,很是方便。

Apache Poi是用Java编写的一款免费开源的跨平台的JavaAPI,该API可以通过Java程序对Office格式文档进行读写操作,可以说是现阶段Java库当中最好用的office处理库了,可能都不用加之一两个字。所以基于Apache Poi的Poi-tl可以让你在word文档的任何地方做任何你想做的事情。

举个例子,如果想生成一个名叫TJ君真棒.docx的文档,并且在文档里包含文本{{title}},只需要一句代码,这句代码也是整个引擎的核心所在:

//核心API采用了极简设计,只需要一行代码
XWPFTemplate.compile("TJ君真棒.docx").render(new HashMap<String, Object>(){{
        put("title""Poi-tl 模板引擎");
}}).writeToFile("out_TJ君真棒.docx");

Poi-tl整体设计采用了Template + data-model = output模式.

Configure提供了模板配置功能,比如语法配置和插件配置:


/**
 * 插件化配置
 * 
 * @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>();

    /**
     * 引用渲染策略
     */

    private List<ReferenceRenderPolicy<?>> referencePolicies = new ArrayList<>();

    /**
     * 语法前缀
     */

    private String gramerPrefix = "{{";
    /**
     * 语法后缀
     */

    private String gramerSuffix = "}}";

    /**
     * 默认支持中文、字母、数字、下划线的正则
     */

    private String grammerRegex = DEFAULT_GRAMER_REGEX;

    /**
     * 模板表达式模式,默认为POI_TL_MODE
     */

    private ELMode elMode = ELMode.POI_TL_STANDARD_MODE;

    /**
     * 渲染数据校验不通过时的处理策略
     * <ul>
     * <li>DiscardHandler: 什么都不做</li>
     * <li>ClearHandler: 清空标签</li>
     * <li>AbortHandler: 抛出异常</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());
    }

    /**
     * 创建默认配置
     * 
     * @return
     */

    public static Configure createDefault() {
        return newBuilder().build();
    }

    /**
     * 构建器
     * 
     * @return
     */

    public static ConfigureBuilder newBuilder() {
        return new ConfigureBuilder();
    }

    /**
     * 新增或变更语法插件
     * 
     * @param c
     *            语法
     * @param policy
     *            策略
     */

    public Configure plugin(char c, RenderPolicy policy) {
        defaultPolicys.put(Character.valueOf(c), policy);
        return this;
    }

    /**
     * 新增或变更语法插件
     * 
     * @param symbol
     *            语法
     * @param policy
     *            策略
     * @return
     */

    Configure plugin(GramerSymbol symbol, RenderPolicy policy) {
        defaultPolicys.put(symbol.getSymbol(), policy);
        return this;
    }

    /**
     * 自定义模板和策略
     * 
     * @param tagName
     *            模板名称
     * @param policy
     *            策略
     */

    public void customPolicy(String tagName, RenderPolicy policy) {
        customPolicys.put(tagName, policy);
    }

    /**
     * 新增引用渲染策略
     * 
     * @param policy
     */

    public void referencePolicy(ReferenceRenderPolicy<?> policy) {
        referencePolicies.add(policy);
    }

    /**
     * 获取标签策略
     * 
     * @param tagName
     *            模板名称
     * @param sign
     *            语法
     */

    // 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提供了模板解析功能:

/**
 * 模板解析器
 * 
 * @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();
    }

}

最后,RenderPolicy是渲染策略扩展点,Render模块提供了RenderDataCompute表达式计算扩展点,通过RenderPolicy对每个标签进行渲染。

当然,如果想将Poi-tl用的好的话,还是要花一点时间来研究其中具体模块的语法,好在Poi-tl提供详细的示例代码讲解,小伙伴们只要用心学一下,很快就能掌握的

到底能不能让小伙伴们减轻写文档的压力呢?记得用过之后来给TJ君反馈哦!想用的小伙伴,完整项目地址在这里:

点击下方卡片,关注公众号“TJ君

回复“生成word”,获取仓库地址

关注我,每天了解一个牛x、好用、有趣的东东

往期推荐

GitHub高赞,一款足以取代迅雷的开源下载工具

Copilot 爆裂更新,通过注释自动生成代码现已支持 IntelliJ IDEA、PyCharm

可能是你用过最好用的Redis客户端!

IDEA中文字符自动转换插件,再也不用担心程序因为中文字符报错了!

IDEA编码自动注释工具,让你的开发更有效率

十万伏特!让你的操作终端变成宝可梦!


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存