用JAVA寫(xiě)的word模板自動(dòng)生成引擎
大家好,我是TJ
一個(gè)勵(lì)志推薦10000款開(kāi)源項(xiàng)目與工具的程序員

TJ君做項(xiàng)目的時(shí)候最頭疼什么?當(dāng)然是寫(xiě)各種文檔啦,尤其是在大公司做項(xiàng)目,各種規(guī)范文檔不可少,雖然說(shuō)一個(gè)成熟的項(xiàng)目管理過(guò)程中的確是要依靠各種文檔來(lái)明確項(xiàng)目里程碑及具體的設(shè)計(jì)確認(rèn)和需求分工,但是TJ君還是更喜歡把時(shí)間花在開(kāi)發(fā)代碼上。
尤其是有些文檔的格式都差不多,那是不是我們程序猿可以發(fā)揮特長(zhǎng),用程序來(lái)生成輸出指定的word文檔,減少自己的手寫(xiě)時(shí)間呢?
當(dāng)然是可以的!
今天TJ君就要給大家分享一款Word專(zhuān)用的模板引擎,Poi-tl(Poi-template-language)。這款引擎基于A(yíng)pache Poi,可以根據(jù)用戶(hù)輸入的內(nèi)容直接生成相應(yīng)的word文檔,很是方便。
Apache Poi是用Java編寫(xiě)的一款免費(fèi)開(kāi)源的跨平臺(tái)的JavaAPI,該API可以通過(guò)Java程序?qū)ffice格式文檔進(jìn)行讀寫(xiě)操作,可以說(shuō)是現(xiàn)階段Java庫(kù)當(dāng)中最好用的office處理庫(kù)了,可能都不用加之一兩個(gè)字。所以基于A(yíng)pache Poi的Poi-tl可以讓你在word文檔的任何地方做任何你想做的事情。
舉個(gè)例子,如果想生成一個(gè)名叫TJ君真棒.docx的文檔,并且在文檔里包含文本{{title}},只需要一句代碼,這句代碼也是整個(gè)引擎的核心所在:
//核心API采用了極簡(jiǎn)設(shè)計(jì),只需要一行代碼
XWPFTemplate.compile("TJ君真棒.docx").render(new?HashMap(){{
????????put("title",?"Poi-tl?模板引擎");
}}).writeToFile("out_TJ君真棒.docx");
Poi-tl整體設(shè)計(jì)采用了Template + data-model = output模式.

Configure提供了模板配置功能,比如語(yǔ)法配置和插件配置:
/**
?*?插件化配置
?*?
?*?@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?customPolicys?=?new?HashMap();
????//?Low?priority
????private?Map?defaultPolicys?=?new?HashMap();
????/**
?????*?引用渲染策略
?????*/
????private?List>?referencePolicies?=?new?ArrayList<>();
????/**
?????*?語(yǔ)法前綴
?????*/
????private?String?gramerPrefix?=?"{{";
????/**
?????*?語(yǔ)法后綴
?????*/
????private?String?gramerSuffix?=?"}}";
????/**
?????*?默認(rèn)支持中文、字母、數(shù)字、下劃線(xiàn)的正則
?????*/
????private?String?grammerRegex?=?DEFAULT_GRAMER_REGEX;
????/**
?????*?模板表達(dá)式模式,默認(rèn)為POI_TL_MODE
?????*/
????private?ELMode?elMode?=?ELMode.POI_TL_STANDARD_MODE;
????/**
?????*?渲染數(shù)據(jù)校驗(yàn)不通過(guò)時(shí)的處理策略
?????*?
?????*?- DiscardHandler:?什么都不做
?????*?- ClearHandler:?清空標(biāo)簽
?????*?- AbortHandler:?拋出異常
?????*?
?????*/
????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());
????}
????/**
?????*?創(chuàng)建默認(rèn)配置
?????*?
?????*?@return
?????*/
????public?static?Configure?createDefault()?{
????????return?newBuilder().build();
????}
????/**
?????*?構(gòu)建器
?????*?
?????*?@return
?????*/
????public?static?ConfigureBuilder?newBuilder()?{
????????return?new?ConfigureBuilder();
????}
????/**
?????*?新增或變更語(yǔ)法插件
?????*?
?????*?@param?c
?????*????????????語(yǔ)法
?????*?@param?policy
?????*????????????策略
?????*/
????public?Configure?plugin(char?c,?RenderPolicy?policy)?{
????????defaultPolicys.put(Character.valueOf(c),?policy);
????????return?this;
????}
????/**
?????*?新增或變更語(yǔ)法插件
?????*?
?????*?@param?symbol
?????*????????????語(yǔ)法
?????*?@param?policy
?????*????????????策略
?????*?@return
?????*/
????Configure?plugin(GramerSymbol?symbol,?RenderPolicy?policy)?{
????????defaultPolicys.put(symbol.getSymbol(),?policy);
????????return?this;
????}
????/**
?????*?自定義模板和策略
?????*?
?????*?@param?tagName
?????*????????????模板名稱(chēng)
?????*?@param?policy
?????*????????????策略
?????*/
????public?void?customPolicy(String?tagName,?RenderPolicy?policy)?{
????????customPolicys.put(tagName,?policy);
????}
????/**
?????*?新增引用渲染策略
?????*?
?????*?@param?policy
?????*/
????public?void?referencePolicy(ReferenceRenderPolicy>?policy)?{
????????referencePolicies.add(policy);
????}
????/**
?????*?獲取標(biāo)簽策略
?????*?
?????*?@param?tagName
?????*????????????模板名稱(chēng)
?????*?@param?sign
?????*????????????語(yǔ)法
?????*/
????//?Query?Operations
????public?RenderPolicy?getPolicy(String?tagName,?Character?sign)?{
????????RenderPolicy?policy?=?getCustomPolicy(tagName);
????????return?null?==?policy???getDefaultPolicy(sign)?:?policy;
????}
????public?List>?getReferencePolicies()?{
????????return?referencePolicies;
????}
????
????private?RenderPolicy?getCustomPolicy(String?tagName)?{
????????return?customPolicys.get(tagName);
????}
????private?RenderPolicy?getDefaultPolicy(Character?sign)?{
????????return?defaultPolicys.get(sign);
????}
????public?Map?getDefaultPolicys()? {
????????return?defaultPolicys;
????}
????public?Map?getCustomPolicys()? {
????????return?customPolicys;
????}
????public?Set?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?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?visitDocument(XWPFDocument?doc)? {
????????if?(null?==?doc)?return?null;
????????this.eleTemplates?=?new?ArrayList();
????????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?headers) ?{
????????if?(null?==?headers)?return;
????????for?(XWPFHeader?header?:?headers)?{
????????????visitParagraphs(header.getParagraphs());
????????????visitTables(header.getTables());
????????}
????}
????void?visitFooters(List?footers) ?{
????????if?(null?==?footers)?return;
????????for?(XWPFFooter?footer?:?footers)?{
????????????visitParagraphs(footer.getParagraphs());
????????????visitTables(footer.getTables());
????????}
????}
????void?visitParagraphs(List?paragraphs) ?{
????????if?(null?==?paragraphs)?return;
????????for?(XWPFParagraph?paragraph?:?paragraphs)?{
????????????visitParagraph(paragraph);
????????}
????}
????void?visitTables(List?tables) ?{
????????if?(null?==?tables)?return;
????????for?(XWPFTable?tb?:?tables)?{
????????????visitTable(tb);
????????}
????}
????void?visitTable(XWPFTable?table)?{
????????if?(null?==?table)?return;
????????List?rows?=?table.getRows();
????????if?(null?==?rows)?return;
????????for?(XWPFTableRow?row?:?rows)?{
????????????List?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?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??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?gramerChar?=?new?ArrayList(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是渲染策略擴(kuò)展點(diǎn),Render模塊提供了RenderDataCompute表達(dá)式計(jì)算擴(kuò)展點(diǎn),通過(guò)RenderPolicy對(duì)每個(gè)標(biāo)簽進(jìn)行渲染。
當(dāng)然,如果想將Poi-tl用的好的話(huà),還是要花一點(diǎn)時(shí)間來(lái)研究其中具體模塊的語(yǔ)法,好在Poi-tl提供詳細(xì)的示例代碼講解,小伙伴們只要用心學(xué)一下,很快就能掌握的


到底能不能讓小伙伴們減輕寫(xiě)文檔的壓力呢?記得用過(guò)之后來(lái)給TJ君反饋哦!想用的小伙伴,完整項(xiàng)目地址在這里:
點(diǎn)擊下方卡片,關(guān)注公眾號(hào)“TJ君”
回復(fù)“生成word”,獲取倉(cāng)庫(kù)地址
關(guān)注我,每天了解一個(gè)牛x、好用、有趣的東東
往期推薦
