先看看常规使用poi生成word的一些方法
// 创建文档对象
XWPFDocument docxDocument = new XWPFDocument();
// 创建段落对象
XWPFParagraph paragraphX = docxDocument.createParagraph();
// 创建文本对象
XWPFRun runX = paragraphX.createRun();
主要是思路是将某个模板(自己定义的)上传至服务器,然后解析成对应的ftl文件,之后服务在做报告时,就可以直接拿取对应的ftl文件来生成报告。这种方式会比较灵活,现在用的也比较多。但是对于一份简单的,不需要ftl的文件来作为中间生成文件时,那么可能会比较难了。比如,接下来我们要学习的,使用word的模板作为报告模板,然后直接生成需要的报告。这个思路大概是:用户直接设计一个word模板,然后上传至服务器,服务器将其直接保存到对应的文件夹下;在要生成报告时,直接拿word的模板来填充数据。这样,好像也不是很难,但是想想细节上的地方,可能很多东西就不好做了。
我们在使用word作为一份模板时,第一步要解决的就是向模板中定义的字段替换成自己要填充的数据,如:${name}要换成自己的名字;其次就是word中也有表格,如何向word上直接画出表格;再次,表格的行列要怎么合并; 这些都是我们开发中会遇到的问题。
public void replaceParagraph(XWPFParagraph xWPFParagraph, Map<String, Object> parametersMap) {
List<XWPFRun> runs = xWPFParagraph.getRuns();
String xWPFParagraphText = xWPFParagraph.getText();
//正则匹配字符串{****}
String regEx = "\\{.+?\\}";
Pattern pattern = Pattern.compile(regEx);
Matcher matcher = pattern.matcher(xWPFParagraphText);
if (matcher.find()) {
// 查找到有标签才执行替换
int beginRunIndex = xWPFParagraph.searchText("{", new PositionInParagraph()).getBeginRun();// 标签开始run位置
int endRunIndex = xWPFParagraph.searchText("}", new PositionInParagraph()).getEndRun();// 结束标签
StringBuffer key = new StringBuffer();
if (beginRunIndex == endRunIndex) {
// {**}在一个run标签内
XWPFRun beginRun = runs.get(beginRunIndex);
String beginRunText = beginRun.text();
int beginIndex = beginRunText.indexOf("{");
int endIndex = beginRunText.indexOf("}");
int length = beginRunText.length();
if (beginIndex == 0 && endIndex == length - 1) {
// 该run标签只有{**}
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
// 设置文本
key.append(beginRunText.substring(1, endIndex));
insertNewRun.setText(getValueBykey(key.toString(), parametersMap));
xWPFParagraph.removeRun(beginRunIndex + 1);
} else {
// 该run标签为**{**}** 或者 **{**} 或者{**}**,替换key后,还需要加上原始key前后的文本
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
// 设置文本
key.append(beginRunText.substring(beginRunText.indexOf("{") + 1, beginRunText.indexOf("}")));
String textString = beginRunText.substring(0, beginIndex) + getValueBykey(key.toString(), parametersMap)
+ beginRunText.substring(endIndex + 1);
insertNewRun.setText(textString);
xWPFParagraph.removeRun(beginRunIndex + 1);
}
} else {
// {**}被分成多个run
// 先处理起始run标签,取得第一个{key}值
XWPFRun beginRun = runs.get(beginRunIndex);
String beginRunText = beginRun.text();
int beginIndex = beginRunText.indexOf("{");
if (beginRunText.length() > 1) {
key.append(beginRunText.substring(beginIndex + 1));
}
ArrayList<Integer> removeRunList = new ArrayList<Integer>();//需要移除的run
// 处理中间的run
for (int i = beginRunIndex + 1; i < endRunIndex; i++) {
XWPFRun run = runs.get(i);
String runText = run.text();
key.append(runText);
removeRunList.add(i);
}
// 获取endRun中的key值
XWPFRun endRun = runs.get(endRunIndex);
String endRunText = endRun.text();
int endIndex = endRunText.indexOf("}");
//run中**}或者**}**
if (endRunText.length() > 1 && endIndex != 0) {
key.append(endRunText.substring(0, endIndex));
}
// 取得key值后替换标签
// 先处理开始标签
if (beginRunText.length() == 2) {
// run标签内文本{
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
// 设置文本
insertNewRun.setText(getValueBykey(key.toString(), parametersMap));
xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
} else {
// 该run标签为**{**或者 {** ,替换key后,还需要加上原始key前的文本
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(beginRunIndex);
insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
// 设置文本
String textString = beginRunText.substring(0, beginRunText.indexOf("{")) + getValueBykey(key.toString(), parametersMap);
// 分行处理 有#的时候分行(给一个标记)
if (textString.contains("#")) {
String[] textStrings = textString.split("#");
for (int i = 0; i < textStrings.length; i++) {
insertNewRun.setText(textStrings[i]);
insertNewRun.addBreak();// 换行
}
} else {
insertNewRun.setText(textString);
}
xWPFParagraph.removeRun(beginRunIndex + 1);//移除原始的run
}
// 处理结束标签
if (endRunText.length() == 1) {
// run标签内文本只有}
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
// 设置文本
insertNewRun.setText("");
xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run
} else {
// 该run标签为**}**或者 }** 或者**},替换key后,还需要加上原始key后的文本
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex);
insertNewRun.getCTR().setRPr(endRun.getCTR().getRPr());
// 设置文本
String textString = endRunText.substring(endRunText.indexOf("}") + 1);
insertNewRun.setText(textString);
xWPFParagraph.removeRun(endRunIndex + 1);//移除原始的run
}
// 处理中间的run标签
for (int i = 0; i < removeRunList.size(); i++) {
XWPFRun xWPFRun = runs.get(removeRunList.get(i));//原始run
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(removeRunList.get(i));
insertNewRun.getCTR().setRPr(xWPFRun.getCTR().getRPr());
insertNewRun.setText("");
xWPFParagraph.removeRun(removeRunList.get(i) + 1);//移除原始的run
}
}// 处理${**}被分成多个run
replaceParagraph(xWPFParagraph, parametersMap);
}
}
表格的操作方式和上面的代码也是大同小异,也是要写一个标签,才能知道哪里需要循环,从上面的代码就可以看出用原生poi操作word显得非常复杂,这个时候我就要给大家推荐一种新的poi的操作, poi-template-language.
模板和插件构建了整个Poi-tl 的核心。
基于Apache POI进行了一些增强封装,如合并多个Word文档、合并单元格、图片处理等,插件机制使得可以基于模板引擎特性扩展出更丰富的功能。
com.deepoove
poi-tl
1.4.2
从一个超级简单的例子开始:把{{title}}替换成"Poi-tl 模板引擎"。
// 核心API采用了极简设计,只需要一行代码
XWPFTemplate template = XWPFTemplate.compile("~/template.docx").render(new HashMap<String, Object>(){{
put("title", "Poi-tl 模板引擎");
}});
FileOutputStream out = new FileOutputStream("out_template.docx");
template.write(out);
out.flush();
out.close();
template.close();
所有的语法结构都是以 {{ 开始,以 }} 结束。
TextRenderData
或String
数据模型,继承模板样式的同时,也可以自定义颜色、字体等样式。
Map<String, Object> datas = new HashMap<String, Object>();
datas.put("author", new TextRenderData("00FF00", "Sayi卅一"));
datas.put("introduce", "http://www.deepoove.com");
//本地图片
put("localPicture", new PictureRenderData(120, 120, "src/test/resources/sayi.png"));
//本地图片byte数据
put("localBytePicture", new PictureRenderData(100, 120, ".png", BytePictureUtils.getLocalByteArray(new File("src/test/resources/logo.png"))));
RowRenderData header = RowRenderData.build(new TextRenderData("FFFFFF", "姓名"), new TextRenderData("FFFFFF", "学历"));
RowRenderData row = RowRenderData.build("张三", "研究生");
put("table", new MiniTableRenderData(header, Arrays.asList(row)));
put("feature", new NumbericRenderData(new ArrayList<TextRenderData>() {
{
add(new TextRenderData("Plug-in grammar, add new grammar by yourself"));
add(new TextRenderData("Supports word text, header, footer..."));
add(new TextRenderData("Templates, not just templates, but also style templates"));
}
}));
DocxRenderData
数据模型,支持Word文档的合并,文档模板(重复文档段落)被集合数据循环渲染后合并。
List<SegmentData> segments = new ArrayList<SegmentData>();
SegmentData s1 = new SegmentData();
s1.setTitle("经常抱怨的自己");
s1.setContent("每个人生活得都不容易,经常向别人抱怨的人,说白了就是把对方当做“垃圾场”,你一股脑地将自己的埋怨与不满倒给别人,自己倒是爽了,你有考虑过对方的感受吗?对方的脸上可能一笑了之,但是心里可能有一万只草泥马奔腾而过。");
segments.add(s1);
SegmentData s2 = new SegmentData();
s2.setTitle("拖拖拉拉的自己");
s2.setContent("能够今天做完的事情,不要拖到明天,你的事情没有任何人有义务去帮你做;不要做“宅男”、不要当“宅女”,放假的日子约上三五好友出去转转;经常动手做家务,既能分担伴侣的负担,又有一个干净舒适的环境何乐而不为呢?");
segments.add(s2);
put("docx_word", new DocxRenderData(new File("~/segment.docx"), segments));
/**
* 付款通知书:表格操作示例
*/
public class PaymentExample {
PaymentData datas = new PaymentData();
Style headTextStyle = new Style();
TableStyle headStyle = new TableStyle();
TableStyle rowStyle = new TableStyle();
@Before
public void init() {
headTextStyle.setFontFamily("Hei");
headTextStyle.setFontSize(9);
headTextStyle.setColor("7F7F7F");
headStyle.setBackgroundColor("F2F2F2");
headStyle.setAlign(STJc.CENTER);
rowStyle = new TableStyle();
rowStyle.setAlign(STJc.CENTER);
datas.setNO("KB.6890451");
datas.setID("ZHANG_SAN_091");
datas.setTaitou("深圳XX家装有限公司");
datas.setConsignee("丙丁");
datas.setSubtotal("8000");
datas.setTax("600");
datas.setTransform("120");
datas.setOther("250");
datas.setUnpay("6600");
datas.setTotal("总共:7200");
RowRenderData header = RowRenderData.build(new TextRenderData("日期", headTextStyle),
new TextRenderData("订单编号", headTextStyle), new TextRenderData("销售代表", headTextStyle),
new TextRenderData("离岸价", headTextStyle), new TextRenderData("发货方式", headTextStyle),
new TextRenderData("条款", headTextStyle), new TextRenderData("税号", headTextStyle));
header.setStyle(headStyle);
RowRenderData row = RowRenderData.build("2018-06-12", "SN18090", "李四", "5000元", "快递", "附录A", "T11090");
row.setStyle(rowStyle);
MiniTableRenderData miniTableRenderData = new MiniTableRenderData(header, Arrays.asList(row), MiniTableRenderData.WIDTH_A4_MEDIUM_FULL);
miniTableRenderData.setStyle(headStyle);
datas.setOrder(miniTableRenderData);
DetailData detailTable = new DetailData();
RowRenderData good = RowRenderData.build("4", "墙纸", "书房+卧室", "1500", "/", "400", "1600");
good.setStyle(rowStyle);
List<RowRenderData> goods = Arrays.asList(good, good, good);
RowRenderData labor = RowRenderData.build("油漆工", "2", "200", "400");
labor.setStyle(rowStyle);
List<RowRenderData> labors = Arrays.asList(labor, labor, labor, labor);
detailTable.setGoods(goods);
detailTable.setLabors(labors);
datas.setDetailTable(detailTable);
}
@Test
public void testResumeExample() throws Exception {
Configure config = Configure.newBuilder().customPolicy("detail_table", new DetailTablePolicy()).build();
XWPFTemplate template = XWPFTemplate.compile("src/test/resources/付款通知书.docx", config).render(datas);
FileOutputStream out = new FileOutputStream("e:/out_付款通知书.docx");
template.write(out);
out.flush();
out.close();
template.close();
}
}
public class DetailData {
// 货品数据
private List<RowRenderData> goods;
// 人工费数据
private List<RowRenderData> labors;
public List<RowRenderData> getGoods() {
return goods;
}
public void setGoods(List<RowRenderData> goods) {
this.goods = goods;
}
public List<RowRenderData> getLabors() {
return labors;
}
public void setLabors(List<RowRenderData> labors) {
this.labors = labors;
}
}
// 省略了后面的构造方法
public class PaymentData {
private MiniTableRenderData order;
private String NO;
private String ID;
private String taitou;
private String consignee;
@Name("detail_table")
private DetailData detailTable;
private String subtotal;
private String tax;
private String transform;
private String other;
private String unpay;
private String total;
}