poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。
核心思想是在模板中放一个占位符,在代码中替换该占位符即可。
poi官网地址 点这里
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0</version> <!-- 替换为你想要使用的特定版本 -->
</dependency>
chart分为三种类型,分别是单系列、多系列及组合。
public static void main(String[] args) throws IOException {
Map<String, Object> map = new HashMap<>();
ChartSingleSeriesRenderData demo1 = Charts.ofPie3D("", new String[]{"第一季度", "第二季度", "第三季度", "第四季度"})
.series("", new Number[]{0.3, 0.3, 0.2, 0.2})
.create();
map.put("demo1", demo1);
XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");
}
插入图表模板后,在图表空白区域右键选择设置图表区域格式
->右侧属性选择文本选项
->文本框
->可选文字
,在标题中输入{{demo1}}
,其中{{}}
为替换标签,必须满足该格式,demo
作为属性值,可以修改,注意和代码中map.put("demo1", demo1);
设置的键保持一致即可
此处模板我使用的是wps,wps和office不同,office直接右键
查看可选文字
即可,如下图所示
导出结果如下
public static void main(String[] args) throws IOException {
Map<String, Object> map = new HashMap<>();
ChartMultiSeriesRenderData demo2 = Charts.ofBar("", new String[]{"销售量"})
.addSeries("第一季度", new Number[]{100})
.addSeries("第二季度", new Number[]{200})
.addSeries("第三季度", new Number[]{500})
.addSeries("第四季度", new Number[]{1000})
.create();
map.put("demo2", demo2);
XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");
}
生成的图表如下
Map<String, Object> map = new HashMap<>();
ChartMultiSeriesRenderData demo3 = Charts.ofComboSeries("MyChart", new String[] { "中文", "English" })
.addBarSeries("countries", new Double[] { 15.0, 6.0 })
.addBarSeries("speakers", new Double[] { 223.0, 119.0 })
.addBarSeries("NewBar", new Double[] { 223.0, 119.0 })
.addLineSeries("youngs", new Double[] { 323.0, 89.0 })
.addLineSeries("NewLine", new Double[] { 123.0, 59.0 }).create();
map.put("demo3", demo3);
XWPFTemplate.compile("C:\\Users\\huahua\\Desktop\\2.doc").render(map).writeToFile("C:\\Users\\huahua\\Desktop\\demo_output.doc");
不同的chart配置都大同小异,基本都没什么难度,简单了解下就能上手
表格分为静态表格和动态表格,
当表格的需求比较简单时,可以通过简单的模板替换及列表循环填充表格数据
当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。poi-tl提供了抽象表格策略 DynamicTableRenderPolicy 来实现这样的功能。
以官网模板为例
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import java.util.List;
public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 货品填充数据所在行数
int goodsStartRow = 2;
// 人工费填充数据所在行数
int laborsStartRow = 5;
@Override
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) return;
DetailData detailData = (DetailData) data;
// 人工费
List<RowRenderData> labors = detailData.getLabors();
if (null != labors) {
table.removeRow(laborsStartRow);
// 循环插入行
for (int i = 0; i < labors.size(); i++) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(laborsStartRow);
for (int j = 0; j < 7; j++) insertNewTableRow.createCell();
// 横合并单元格
TableTools.mergeCellsHorizonal(table, laborsStartRow, 0, 3);
// 单行渲染
TableRenderPolicy.Helper.renderRow(table.getRow(laborsStartRow), labors.get(i));
}
}
// 货物
List<RowRenderData> goods = detailData.getGoods();
if (null != goods) {
table.removeRow(goodsStartRow);
for (int i = 0; i < goods.size(); i++) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
for (int j = 0; j < 7; j++) insertNewTableRow.createCell();
TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), goods.get(i));
}
}
}
}
import com.deepoove.poi.expression.Name;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import office.DetailData;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentData {
@Name("detail_table")
private DetailData detailTable;
}
import com.deepoove.poi.data.RowRenderData;
import lombok.Data;
import java.util.List;
@Data
public class DetailData {
// 货品数据
private List<RowRenderData> goods;
// 人工费数据
private List<RowRenderData> labors;
}
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import excel.DetailTablePolicy;
import org.junit.jupiter.api.BeforeEach;
import java.util.Arrays;
import java.util.List;
public class Test {
String resource = "C:\\Users\\huahua\\Desktop\\demo\\table.docx";
PaymentData datas = new PaymentData();
@BeforeEach
public void init() {
datas.setTotal("总共:7200");
DetailData detailTable = new DetailData();
RowRenderData rowRenderData = Rows.of("4", "墙纸", "书房+卧室", "1500", "/", "400", "1600").center().create();
List<RowRenderData> goods = Arrays.asList(rowRenderData, rowRenderData, rowRenderData);
RowRenderData labor = Rows.of("油漆工", "2", "200", "400").center().create();
List<RowRenderData> labors = Arrays.asList(labor, labor, labor, labor);
detailTable.setGoods(goods);
detailTable.setLabors(labors);
datas.setDetailTable(detailTable);
}
@org.junit.jupiter.api.Test
public void testPaymentExample() throws Exception {
Configure config = Configure.builder().bind("detail_table", new DetailTablePolicy()).build();
XWPFTemplate template = XWPFTemplate.compile(resource, config).render(datas);
template.writeToFile("C:\\Users\\huahua\\Desktop\\demo\\table_output.docx");
}
}
这里有个特别需要注意的地方,就是@Name("detail_table")
,这个对象的值一定要和模板中的保持一致,否则在表格对象绑定的时候无法绑定到数据,从 XWPFTemplate template = XWPFTemplate.compile(resource, config).render(datas);
绑定数据的时候传递到自定义测策略中的时候,数据会丢失,底层源码如下
void visit(ElementTemplate eleTemplate) {
RenderPolicy policy = eleTemplate.findPolicy(this.template.getConfig());
Objects.requireNonNull(policy, "Cannot find render policy: [" + eleTemplate.getTagName() + "]");
if (!(policy instanceof DocxRenderPolicy)) {
logger.info("Start render Template {}, Sign:{}, policy:{}", new Object[]{eleTemplate, eleTemplate.getSign(), ClassUtils.getShortClassName(policy.getClass())});
policy.render(eleTemplate, this.renderDataCompute.compute(eleTemplate.getTagName()), this.template);
}
}
this.renderDataCompute.compute(eleTemplate.getTagName()
,其中renderDataCompute
是我们单元测试类中传递的datas
数据,eleTemplate.getTagName()
是word模板中的{{detail_table}}
,从我们传递的数据中获取detail_table
这个属性,并传递给render方法,这就是为什么如果属性名不匹配,render方法取不到值的原因。
至于total
属性为什么不需要,后续再补充
横向单元格合并在行渲染的时候执行,纵向单元格合并在行渲染后执行,下面是一个行渲染的例子,每5行的第一列合并为一列
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) return;
List<DetailData2> dataList = (List<DetailData2>) data;
if (dataList.size() == 0) return;
//设置表格属性
setTableType(table);
// 循环插入行, 有多少条数据就有多少行
//插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
for (int i = dataList.size()-1; i >=0; i--) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
//创建单元格,即一行包含几列
for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
// 单行渲染
RowRenderData rowRenderData = this.getCellList(dataList.get(i));
TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
}
//合并单元格
int start = 3;
for (int i = 0; i < dataList.size()/5; i++) {
//合并table对象从start-start+4行的第0列
TableTools.mergeCellsVertically(table, 0, start, start+4);
start += 5;
}
}
注: render
方法中,行是从后向前渲染的,所以数据需要从后向前遍历,因为案例的数据都是一样的,所以没按照次做法。
支持渲染整个表格,渲染行和渲染单元格三种场景,表格暂时没用到,先不介绍。
渲染行即模板只包含table的表头,下面的行数据需要手动渲染。模板文件可参考动态表格的第一个事例
核心代码如下
for (int i = dataList.size()-1; i >=0; i--) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
//创建单元格,即一行包含几列
for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
// 单行渲染
RowRenderData rowRenderData = this.getCellList(dataList.get(i));
TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
}
完整代码如下,其中包含了table及行和单元格样式的设置
package excel;
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.*;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.*;
import java.util.Arrays;
import java.util.List;
public class DetailTablePolicy2 extends DynamicTableRenderPolicy {
// 货品填充数据所在行数
int goodsStartRow = 3;
@Override
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) return;
List<DetailData2> dataList = (List<DetailData2>) data;
if (dataList.size() == 0) return;
//设置表格属性
setTableType(table);
// 循环插入行, 有多少条数据就有多少行
//插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
for (int i = dataList.size()-1; i >=0; i--) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
//创建单元格,即一行包含几列
for (int j = 0; j < 11; j++) insertNewTableRow.createCell();
// 单行渲染
RowRenderData rowRenderData = this.getCellList(dataList.get(i));
TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), rowRenderData);
}
//合并单元格
int start = 3;
for (int i = 0; i < dataList.size()/5; i++) {
TableTools.mergeCellsVertically(table, 0, start, start+4);
start += 5;
}
}
private void setTableType(XWPFTable table) {
table.setTableAlignment(TableRowAlign.CENTER);
table.setRightBorder(XWPFTable.XWPFBorderType.DOUBLE, 1, 0, "898989");
table.setLeftBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
table.setBottomBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
table.setTopBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
table.setInsideHBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
table.setInsideVBorder(XWPFTable.XWPFBorderType.THICK, 1, 0, "898989");
}
public RowRenderData getCellList(DetailData2 detailData2) {
RowRenderData rowRenderData = new RowRenderData();
setRowType(rowRenderData);
rowRenderData.addCell(createCell(detailData2.getName()));
rowRenderData.addCell(createCell(detailData2.getRange()));
operateValue(rowRenderData, detailData2.getNo1(),detailData2.getNo2(),detailData2.getNo3(),detailData2.getNo4(),detailData2.getNo5(),
detailData2.getNo6(),detailData2.getNo7(),detailData2.getNo8(),detailData2.getNo9());
return rowRenderData;
}
private void setRowType(RowRenderData rowRenderData) {
//行样式
RowStyle rowStyle = new RowStyle();
rowRenderData.setRowStyle(rowStyle);
//文本样式
ParagraphStyle defaultParaStyle = new ParagraphStyle();
defaultParaStyle.setAlign(ParagraphAlignment.CENTER);
//单元格样式
CellStyle defaultCellStyle = new CellStyle();
defaultCellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
defaultCellStyle.setDefaultParagraphStyle(defaultParaStyle);
rowStyle.setDefaultCellStyle(defaultCellStyle);
}
private CellRenderData createCell(Object text) {
return Cells.of(String.valueOf(text)).create();
}
private void operateValue(RowRenderData rowRenderData, int... values) {
Arrays.stream(values).mapToObj((value) -> {
CellRenderData cellRenderData = Cells.of(String.valueOf(value)).create();
CellStyle cellStyle = new CellStyle();
if (value > 50 ) {
//单元格背景色
cellStyle.setBackgroundColor("FFFF00");
//文本样式
// paragraphStyle.setDefaultTextStyle(new Style("FF7D7D"));
} else if (value > 0) {
cellStyle.setBackgroundColor("FF7D7D");
} else {
cellStyle.setBackgroundColor("92D050");
}
cellRenderData.setCellStyle(cellStyle);
return cellRenderData;
}).forEach(cellRenderData -> rowRenderData.addCell(cellRenderData));
}
}
渲染单元格同比渲染行简单一些,因为模板是完整的,我们只需要像渲染静态表格那样,替换单元格的内容及样式即可。这里为什么不使用静态表格,是因为静态表格只能修改文本及其样式,这里我们需要修改单元格的背景色。
for (int i = dataList.size()-1; i >=0; i--) {
int[] values = dataList.get(i);
XWPFTableRow insertNewTableRow = table.getRow(goodsStartRow);
// 单行渲染
for (int j = 2; j < 11; j++) {
CellRenderData cellRenderData = this.getCell(values[j-2]);
TableRenderPolicy.Helper.renderCell(insertNewTableRow.getCell(j), cellRenderData, cellRenderData.getCellStyle(), defaultTextStyle);
}
goodsStartRow++;
}
完整代码如下
package excel;
import com.deepoove.poi.data.CellRenderData;
import com.deepoove.poi.data.Cells;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.style.CellStyle;
import com.deepoove.poi.data.style.ParagraphStyle;
import com.deepoove.poi.data.style.RowStyle;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.*;
import java.util.Arrays;
import java.util.List;
public class DetailTablePolicy3 extends DynamicTableRenderPolicy {
// 货品填充数据所在行数
int goodsStartRow = 3;
@Override
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) return;
List<int[]> dataList = (List<int[]>) data;
if (dataList.size() == 0) return;
Style defaultTextStyle = new Style();
// 循环插入行, 有多少条数据就有多少行
//插入的时候是倒序插入的,所以循环遍历的时候倒序遍历
for (int i = dataList.size()-1; i >=0; i--) {
int[] values = dataList.get(i);
XWPFTableRow insertNewTableRow = table.getRow(goodsStartRow);
// 单行渲染
for (int j = 2; j < 11; j++) {
CellRenderData cellRenderData = this.getCell(values[j-2]);
TableRenderPolicy.Helper.renderCell(insertNewTableRow.getCell(j), cellRenderData, cellRenderData.getCellStyle(), defaultTextStyle);
}
goodsStartRow++;
}
}
private CellRenderData getCell(int value) {
CellRenderData cellRenderData = Cells.of(String.valueOf(value)).create();
CellStyle cellStyle = new CellStyle();
cellStyle.setVertAlign(XWPFTableCell.XWPFVertAlign.CENTER);
if (value > 50 ) {
//单元格背景色
cellStyle.setBackgroundColor("FFFF00");
} else if (value > 0) {
cellStyle.setBackgroundColor("FF7D7D");
} else {
cellStyle.setBackgroundColor("92D050");
}
cellRenderData.setCellStyle(cellStyle);
ParagraphStyle defaultParagraphStyle = new ParagraphStyle();
defaultParagraphStyle.setAlign(ParagraphAlignment.CENTER);
cellStyle.setDefaultParagraphStyle(defaultParagraphStyle);
return cellRenderData;
}
}
模板文件参考如下表格,使用于这种表格行数固定及有几列固定值的场景: