poi-tl 入门示例
可参照poi-tl 官方api文档:poi-tl
引入maven 后
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.11.0</version>
</dependency>
1.准备word模板
2.代码
public class PoiTest {
public static void main(String[] args) throws IOException {
//要写入模板的数据
Map<String,Object> exampleData = new HashMap<>();
exampleData.put("username","admin");
exampleData.put("password","123456");
FileInputStream inputStream = new FileInputStream("D:/test_word/example1.docx");
XWPFTemplate template = XWPFTemplate.compile(inputStream)
.render(exampleData);
//文件输出流
FileOutputStream out = new FileOutputStream("D:/test_word/example1.docx");
template.write(out);
out.flush();
out.close();
template.close();
}
}
自定义列表序号
1. poi-tl 支持的序号列表,直接使用文档种说明即可
2. 要是没有,可以利用NumberingFormat类去生成指定的样式;例如生成 a) b)的序号
3. 同样按照官方api指定{{*var}}后
4. 具体代码如下
public class test {
public static void main(String[] args) throws IOException {
//要写入模板的数据
Map<String, Object> datas1 = new HashMap<String, Object>();
//列表集合
List<String> varList = Arrays.asList("序号1",
"序号2");
//自定义序号的样式为 a) b) c)
NumberingFormat.Builder builder = NumberingFormat.
builder("%{0})") //%{0}) 可以指定自己需要的样式
.withNumFmt(NumFormat.LOWER_LETTER); //小写字母
NumberingFormat numberingFormat = builder.build(0);
Numberings.NumberingBuilder of = Numberings.of(numberingFormat);//a) b) c)
//列表 数据赋值
varList.forEach(s -> of.addItem(s));
NumberingRenderData numberingRenderData = of.create();
datas1.put("var", numberingRenderData);
XWPFTemplate.compile("D:/test_word/test.docx")
.render(datas1).writeToFile("D:/test_word/test1.docx");
}
}
自定义表格宽度和表格合并
poi-tl导出的word表格默认宽度是自适应的,若是想要指定列的宽度增加,可以采用如下方式:
最终实现的word表格
1. 在word模板文件中,生成表格位置添加标签:{{#check_table}}
2. 具体整体代码如下
public class test {
public static void main(String[] args) throws IOException {
Map<String, Object> datas = new HashMap<String, Object>();
// create table
//word的 表格
List tableList = new ArrayList<>();
tableList.add("数据1");
tableList.add("数据2");
tableList.add("数据3");
tableList.add("数据4");
tableList.add("数据5");
tableList.add("数据6");
//验证类型集合
List<String> typeList = Arrays.asList("MC0",
"MC1",
"MC2",
"MC3",
"MC4",
"MC5",
"MC6",
"MC7",
"MC8",
"MC9");
//表头是两行
//第一行数据是固定内容 "序号", "章节号", "项目", "内容", "验证方法", "备注"
List<String> headerCellListTemp1 = Arrays.asList("序号", "章节号", "项目", "内容", "验证方法");
List<String> headerCellList1 = new ArrayList<>(headerCellListTemp1);
//验证方法,可能是多列数据 第一列是验证方法,后面是赋空字符串,用于合并单元格;
//数据类型:验证方法 "" "" ""
//验证类型长度
int typeListSize = typeList.size();
for (int k = 0; k < typeListSize - 1; k++) {
headerCellList1.add(" "); //赋空字符串
}
headerCellList1.add("备注");
String[] headerCell1 = headerCellList1.toArray(new String[headerCellList1.size()]);
RowRenderData header1 = Rows.of(headerCell1).center().create();
//headerCell2 :验证方法对应列,是动态内容
List<String> headerCellListTemp2 = Arrays.asList("序号", "章节号", "项目", "内容");
List<String> headerCellList2 = new ArrayList<>(headerCellListTemp2);
// 验证方法对应列
headerCellList2.addAll(typeList);
//备注对应的列 赋空值
headerCellList2.add(" ");
String[] headerCell2 = headerCellList2.toArray(new String[headerCellList2.size()]);
RowRenderData header2 = Rows.of(headerCell2).center().create();
//表格 列的数量
int cellLength = headerCell1.length;
//无数据只有表头数据 tableList 无值
if (CollectionUtil.isEmpty(tableList)) {
RowRenderData[] RowRenderDataHeader = new RowRenderData[2];
RowRenderDataHeader[0] = header1;
RowRenderDataHeader[1] = header2;
TableRenderData check_table = setTableRenderDataAndColWidth(RowRenderDataHeader, cellLength);
// table 只有表头数据
datas.put("check_table", check_table);
} else {
int length = 2 + tableList.size();
//行数据 数组
RowRenderData[] RowRenderData = new RowRenderData[length];
RowRenderData[0] = header1;
RowRenderData[1] = header2;
for (int i = 0; i < tableList.size(); i++) {
String index = Integer.toString(i + 1);
List<String> tempList = Arrays.asList("A", "B", "C",
"D", "E", "F", "G", "H", "I", "G");
String[] cellString = new String[cellLength];
cellString[0] = index; //序号
cellString[1] = "5.1.1";//标题序号
cellString[2] = "项目"; // 项目
cellString[3] = "说明"; //内容 : 描述
//验证方法对应数据赋值
if (headerCell2.length > 4) {
for (int j = 0; j < tempList.size(); j++) {
cellString[j + 4] = tempList.get(j);
}
}
//备注
cellString[cellLength - 1] = "备注";
RowRenderData rowRenderData = Rows.of(cellString).
center().create();
//行数据赋值
RowRenderData[2 + i] = rowRenderData;
}
TableRenderData check_table = setTableRenderDataAndColWidth(RowRenderData, cellLength);
// table
datas.put("check_table", check_table);
}
XWPFTemplate.compile("D:/test_word/test.docx")
.render(datas).writeToFile("D:/test_word/test1.docx");
}
/**
* 表格赋值,
* 设置列宽和合并单元格
*
* @param rowRenderDataArray
* @param cellLength
* @return
*/
private static TableRenderData setTableRenderDataAndColWidth(RowRenderData[] rowRenderDataArray, Integer cellLength) {
//table赋值set方法需要list
List<RowRenderData> RowRenderDataList = Arrays.asList(rowRenderDataArray);
//设置列宽:
double[] colWidthsCm = new double[cellLength];
for (int i = 0; i < cellLength; i++) {
// "章节号", "项目", "内容" 设置为 2
if (i == 1 || i == 2 || i == 3) {
colWidthsCm[i] = 2D;
} else {
colWidthsCm[i] = 1D;
}
}
//18.450000762939453D A4纸张
TableRenderData check_table = Tables.ofWidth(18.450000762939453D, colWidthsCm).center().create();
check_table.setRows(RowRenderDataList);
//合并单元格
MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = mergeCell(cellLength);
check_table.setMergeRule(mergeCellRuleBuilder.build());
return check_table;
}
/**
* 表格 合并单元格
*
* @param cellLength
* @return
*/
private static MergeCellRule.MergeCellRuleBuilder mergeCell(Integer cellLength) {
/**
* 设置表格合并规则 从0开始
* 1.起始行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
* 2.结束行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
*/
//合并单元格
//合并到【备注】前一列
MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = MergeCellRule.builder();
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 0), MergeCellRule.Grid.of(1, 0));//序号合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 1), MergeCellRule.Grid.of(1, 1));//章节号合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 2), MergeCellRule.Grid.of(1, 2));//项目合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 3), MergeCellRule.Grid.of(1, 3));//内容合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 4), MergeCellRule.Grid.of(0, cellLength - 2));//验证类型合并 第一行
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, cellLength - 1), MergeCellRule.Grid.of(1, cellLength - 1));//备注合并
return mergeCellRuleBuilder;
}
}
3. 设置表格宽度 setTableRenderDataAndColWidth
/**
* 表格赋值,
* 设置列宽和合并单元格
*
* @param rowRenderDataArray
* @param cellLength
* @return
*/
private static TableRenderData setTableRenderDataAndColWidth(RowRenderData[] rowRenderDataArray, Integer cellLength) {
//table赋值set方法需要list
List<RowRenderData> RowRenderDataList = Arrays.asList(rowRenderDataArray);
//设置列宽:
double[] colWidthsCm = new double[cellLength];
for (int i = 0; i < cellLength; i++) {
// "章节号", "项目", "内容" 设置为 2 ;这几列宽度大
if (i == 1 || i == 2 || i == 3) {
colWidthsCm[i] = 2D;
} else {
colWidthsCm[i] = 1D;
}
}
//18.450000762939453D A4纸张
TableRenderData check_table = Tables.ofWidth(18.450000762939453D, colWidthsCm).center().create();
check_table.setRows(RowRenderDataList);
//合并单元格
MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = mergeCell(cellLength);
check_table.setMergeRule(mergeCellRuleBuilder.build());
return check_table;
}
4. 表格合并方法 mergeCell
/**
* 表格 合并单元格
*
* @param cellLength
* @return
*/
private static MergeCellRule.MergeCellRuleBuilder mergeCell(Integer cellLength) {
/**
* 设置表格合并规则 从0开始
* 1.起始行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
* 2.结束行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
*/
//合并单元格
//合并到【备注】前一列
MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = MergeCellRule.builder();
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 0), MergeCellRule.Grid.of(1, 0));//序号合并 上下行合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 1), MergeCellRule.Grid.of(1, 1));//章节号合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 2), MergeCellRule.Grid.of(1, 2));//项目合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 3), MergeCellRule.Grid.of(1, 3));//内容合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, 4), MergeCellRule.Grid.of(0, cellLength - 2));//验证类型合并 第一行
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(0, cellLength - 1), MergeCellRule.Grid.of(1, cellLength - 1));//备注合并
return mergeCellRuleBuilder;
}
自定义标题
标题可以采用原生poi生成,这里实现利用的是poi-tl的Markdown插件中的标题;
最终生成的样式如下
1. word模板内容如下
{{?comment}}
Heading1简介
Heading2简介
Heading3简介
Heading4简介
Heading5简介
{{/comment}}
{{md}}
黑色内容是word模板上的内容
## 2. md文件内容如下
3. 需要生成的标题按照md文件格式拼接为字符串即可
代码如下:
public class test {
public static void main(String[] args) throws IOException {
//要写入模板的数据
Map<String, Object> datas = new HashMap<String, Object>();
//标题的数据 需要按照md文件,标题的格式拼接数据
MarkdownRenderData code = new MarkdownRenderData();
// byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/markdown/basic1.md"));
// String mkdn = new String(bytes);
String mkdn = "### Heading 31\n" +
"#### Heading 41\n" +
"### Heading 32\n" +
"#### Heading 42";
code.setMarkdown(mkdn);
MarkdownStyle style = MarkdownStyle.newStyle();
style.setShowHeaderNumber(true); //设置标题序号
code.setStyle(style);
//标题的数据
datas.put("md", code);
Configure config = Configure.builder().bind("md", new MarkdownRenderPolicy()).build();
XWPFTemplate.compile("src/test/resources/template_finnish/test_title.docx", config)
.render(datas)
.writeToFile("src/test/resources/out/out_markdown2.docx");
}
生成标题其他注意点
1. 自己指定生成标题序号开始位置:重写DocumentVisitor.class 类
下面的内容是修改后的内容:
直接标题赋值时指定值:
或者是修改原生的getHeaderNumber() 方法
private String getHeaderNumber(int level) {
if (level == 1) return "";
String str = StringUtils.join(Arrays.
copyOfRange(headerNumberArray, 2, headerNumberArray.length), '.');
String substring = str.substring(0, (level - 1) * 2 >= str.length() ? str.length() : (level - 1) * 2);
//目前是 5.2.1.
//去除标题序号的. 保持和文档上一致 类似这种5.2.1
if (StringUtils.isNotEmpty(substring)) {
substring = substring.substring(0, substring.length() - 1);
}
return substring + " ";
}
2. 自动生成标题的样式,也可以按照自己想要的方式设置
方式是修改DocumentVisitor 类中的visit()方法
代码如下:展示的代码都是自己修改后的
@Override
public void visit(Heading heading) {
int level = heading.getLevel();
resetHeaderNumberArray(level);
ParagraphBuilder paraOf = Paragraphs.of().styleId(String.valueOf(level)).left().allowWordBreak();
if (style.isShowHeaderNumber()) {
paraOf.addText(Texts.of(getHeaderNumber(level)).style(getStyle(level)).create());
}
DocumentRenderData renderData = parseNode(heading);
if (!renderData.getContents().isEmpty()) {
ParagraphRenderData headerParagraph = (ParagraphRenderData) renderData.getContents().get(0);
// paraOf.addParagraph(headerParagraph);
// paraOf.addText(Texts.of("").bookmark(evalText(headerParagraph)).create());
paraOf.addText(Texts.of(evalText(headerParagraph)).style(getStyle(level)).create());
}
of.addParagraph(paraOf.create());
}
/**
* 自定义标题的样式;
* 不使用该方法,标题样式都是标签{{md}}的样式
*
* @param level
* @return
*/
private Style getStyle(int level) {
Style style = null;
switch (level) {
case 2: //标题1
style = new Style("宋体", 22D); //二号字体
break;
case 3: //标题2
style = new Style("宋体", 16D);//三号字体
break;
case 4: //标题3
style = new Style("宋体", 14D);//四号字体
break;
//其他都是4号字体
default:
style = new Style("宋体", 14D);//二号字体
break;
}
return style;
}
上述代码中,getStyle方法用于指定自己想要修改的样式
更新目录 (wps 不支持,只支持office)
网上搜到的方式,直接采用该方法,可以生成并且更新目录
转载链接如下:
转载生成poi目录
poi-tl的方式
word模板添加标签 {{TOC}}
代码如下
public class AutoMenuWordPoi {
public static void main(String[] args) throws IOException, InvalidFormatException {
//要写入模板的数据
Map<String, Object> datas = new HashMap<String, Object>();
datas.put("TOC", "TOC");
Configure config = Configure.builder().bind("TOC", new TOCRenderPolicy()).build();
XWPFTemplate.compile("src/test/resources/out/out_markdownToc_poi.docx", config)
.render(datas)
.writeToFile("src/test/resources/out/out_menu1.docx");
}
}
也可以word已经生成目录,后面只是修改了添加标题,去更新目录
采用如下代码也可以:
public class AutoMenuWord2 {
private static final String HEADING1 = "1";
public static void main(String[] args) throws IOException, InvalidFormatException {
XWPFDocument doc = new XWPFDocument(new FileInputStream("src/test/resources/out/out_markdown2.docx"));
doc.enforceUpdateFields(); // 更新目录
doc.write(new FileOutputStream("src/test/resources/out/out_markdown3.docx"));
doc.close();
}
}