easypoi介绍
easypoi是一个java的excel和word处理库,主要用于将java对象转换为excel或word文档,并且可以从excel或word文档中读取数据到java对象。本文将重点介绍如何使用easypoi写word文档。
1.引入easypoi依赖
<dependency>
<groupid>cn.afterturn</groupid>
<artifactid>easypoi-base</artifactid>
<version>4.1.3</version>
</dependency>
2.创建word模板
可以使用microsoft word或其他能够创建word模板的软件来设计word文档的模板文件。模板文件中可以使用占位符来标记需要替换的内容。例如,在word文档中可以插入占位符${name}表示需要替换的内容。
3.创建java对象
使用java对象来存放需要写入word文档的数据。
@data
public class user {
private string name;
private int age;
}
4.使用easypoi写入word文档
创建一个wordexportutil对象,利用模板和java对象生成word文档。
import cn.afterturn.easypoi.word.wordexportutil;
import org.apache.poi.xwpf.usermodel.xwpfdocument;
import java.io.fileoutputstream;
import java.util.hashmap;
import java.util.map;
public class writeworddemo {
public static void main(string[] args) throws exception {
// 创建模板和数据对象
user user = new user("tom", 18);
map<string, object> map = new hashmap<>();
map.put("name", user.getname());
map.put("age", user.getage());
// 加载模板文件
xwpfdocument doc = wordexportutil.exportword07(
"user_template.docx", map);
// 输出文件
fileoutputstream fos = new fileoutputstream("user.docx");
doc.write(fos);
fos.close();
doc.close();
}
}
使用wordexportutil.exportword07方法可以生成.docx格式的word文档,第一个参数为word模板文件名,第二个参数是一个map对象,其中包含了模板中所有的占位符和对应的数据。
基于easypoi实现段落循环
easypoi写word模版导出比较方便。但是如果你希望在写word模版的时候将段落进行循环,那就实现不了。
下面的方法基于easypoi实现了word段落的循环。
import cn.afterturn.easypoi.cache.wordcache;
import cn.afterturn.easypoi.entity.imageentity;
import cn.afterturn.easypoi.util.poielutil;
import cn.afterturn.easypoi.util.poipublicutil;
import cn.afterturn.easypoi.word.entity.myxwpfdocument;
import cn.afterturn.easypoi.word.entity.params.excellistentity;
import cn.afterturn.easypoi.word.parse.excel.excelmapparse;
import cn.hutool.core.bean.beanutil;
import org.apache.commons.io.fileutils;
import org.apache.commons.lang3.stringutils;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.xmlcursor;
import org.apache.xmlbeans.xmlobject;
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import java.io.file;
import java.io.fileoutputstream;
import java.util.*;
public class wordparagraphholder {
private static final logger logger = loggerfactory.getlogger(wordparagraphholder1.class);
private xwpfdocument sourcedocument;
private string targetfilename;
private map<string, object> data;
public wordparagraphholder(xwpfdocument sourcedocument, string targetfilename, map<string, object> data) {
this.sourcedocument = sourcedocument;
this.targetfilename = targetfilename;
this.data = data;
}
public string execute() throws exception {
myxwpfdocument second = null;
fileoutputstream fos2 = null;
try {
// 循环生成段落
map<string, object> data = this.data;
this.parseallparagraphic(sourcedocument.getparagraphs(), data);
file targetfile = new file(targetfilename);
fileoutputstream fos = fileutils.openoutputstream(targetfile);
sourcedocument.write(fos);
// 关闭流
fos.close();
sourcedocument.close();
// 循环段落赋值
second = wordcache.getxwpfdocument(targetfile.getpath());
this.evalallparagraphic(second.getparagraphs(), data);
fos2 = fileutils.openoutputstream(targetfile);
second.write(fos2);
} catch (exception e) {
logger.error("生成word段落失败:", e);
return "";
} finally {
// 关闭流
fos2.close();
second.close();
}
return targetfilename;
}
private void evalallparagraphic(list<xwpfparagraph> paragraphs, map<string, object> map) throws exception {
int size = paragraphs.size();
map<string, integer> indexmap = new hashmap<>();
for (int i = 0; i < size; ++i) {
xwpfparagraph paragraph = (xwpfparagraph) paragraphs.get(i);
logger.info("段落内容:{}", paragraph.gettext());
if (paragraph.gettext().indexof("(") != -1) {
object obj = checkthisparagraphneediterator(paragraph, map);
string listkey = getparagraphlistkey(paragraph);
integer index = indexmap.getordefault(listkey, 0);
if (objects.nonnull(obj) && obj instanceof list && index < ((list) obj).size()) {
object o = ((list) obj).get(index);
parseparagraph(paragraph, o, listkey);
index++;
indexmap.put(listkey, index);
}
}
}
}
private void parseallparagraphic(list<xwpfparagraph> paragraphs, map<string, object> map) throws exception {
int size = paragraphs.size();
list<xwpfparagraph> paragraphlist = new arraylist<>();
// 循环找出需要复制的段落
for (int i = 0; i < size; ++i) {
xwpfparagraph paragraph = (xwpfparagraph) paragraphs.get(i);
if (paragraph.gettext().indexof("(") != -1) {
object obj = checkthisparagraphneediterator(paragraph, map);
if (objects.nonnull(obj) && obj instanceof list) {
paragraphlist.add(paragraph);
}
}
}
// 复制段落
for (xwpfparagraph paragraph : paragraphlist) {
system.out.println(paragraph.gettext());
object obj = checkthisparagraphneediterator(paragraph, map);
addparagraph(paragraph, (list) obj);
// 删除模板段落
xwpfdocument document = paragraph.getdocument();
document.removebodyelement(document.getposofparagraph(paragraph));
}
}
/**
* 复制段落
*
* @param source 原段落
* @param doc
*/
public xwpfparagraph createparagraph(xwpfparagraph source, xwpfdocument doc) {
// 使用游标创建一个新行
xmlcursor cursor = source.getctp().newcursor();
xwpfparagraph newparagraph = doc.insertnewparagraph(cursor);
newparagraph.getctp().set(source.getctp().copy());
return newparagraph;
}
private object checkthisparagraphneediterator(xwpfparagraph paragraph, map<string, object> map) throws exception {
string text = paragraph.gettext().trim();
if (text != null && text.contains("fe:") && text.startswith("(")) {
text = text.replace("!fe:", "").replace("$fe:", "").replace("fe:", "").replace("(", "");
string[] keys = text.replaceall("\\s{1,}", " ").trim().split(" ");
object result = poipublicutil.getparamsvalue(keys[0], map);
return objects.nonnull(result) ? result : new arraylist(0);
} else {
return null;
}
}
private string getparagraphlistkey(xwpfparagraph paragraph) throws exception {
string text = paragraph.gettext().trim();
if (text != null && text.contains("fe:") && text.startswith("(")) {
text = text.replace("!fe:", "").replace("$fe:", "").replace("fe:", "").replace("(", "");
string[] keys = text.replaceall("\\s{1,}", " ").trim().split(" ");
return keys[0];
} else {
return null;
}
}
/**
* 赋值段落
*
* @param paragraph
* @param obj
* @throws exception
*/
public void parseparagraph(xwpfparagraph paragraph, object obj, string listkey) throws exception {
string listname = paragraph.gettext().trim();
boolean contains = listname.contains("fe:");
if (!contains) {
return;
}
map<string, object> objectmap = beanutil.beantomap(obj);
parsethisparagraph(paragraph, objectmap, listkey);
}
/**
* 增加段落
*
* @param paragraph
* @param list
* @throws exception
*/
public void addparagraph(xwpfparagraph paragraph, list<object> list) throws exception {
xwpfparagraph currentparagraph = paragraph;
system.out.println("start for each data list :" + list.size());
iterator var11 = list.iterator();
while (var11.hasnext()) {
object obj = var11.next();
this.createparagraph(currentparagraph, currentparagraph.getdocument());
}
}
/**
* 遍历段落赋值
*
* @param paragraph
* @param map
* @throws exception
*/
private void parsethisparagraph(xwpfparagraph paragraph, map<string, object> map, string listkey) throws exception {
xwpfrun currentrun = null;
string currenttext = "";
boolean isfinde = false;
list<integer> runindex = new arraylist();
xwpfrun prerun = null;
for (int i = 0; i < paragraph.getruns().size(); ++i) {
xwpfrun run = (xwpfrun) paragraph.getruns().get(i);
string text = run.gettext(0);
if (!stringutils.isempty(text)) {
if (isfinde) {
currenttext = currenttext + text;
if (currenttext.indexof("[") == -1) {
isfinde = false;
runindex.clear();
} else {
runindex.add(i);
}
if (currenttext.indexof("]") != -1) {
this.changevalues(paragraph, currentrun, currenttext, runindex, map);
currenttext = "";
isfinde = false;
}
} else if (text.indexof("[") >= 0) {
currenttext = text;
isfinde = true;
currentrun = run;
} else {
currenttext = "";
}
if (currenttext.indexof("]") != -1) {
this.changevalues(paragraph, currentrun, currenttext, runindex, map);
isfinde = false;
}
}
// 去除多余的字符串
if (!stringutils.isempty(text)) {
if (text.indexof("(") != -1) {
prerun = run;
} else if (text.indexof("$fe") != -1) {
// run.settext("", 0);
// 清除第一个(
prerun.settext("", 0);
// 防止自定义前缀未被拆分为多个run,根据自定义关键字删除多余前缀。例如($fe:resultlist
if (text.indexof(listkey) != -1) {
string lasttext = run.gettext(0);
if (stringutils.isnotblank(lasttext) && text.indexof(listkey) != -1) {
string replace = lasttext.substring(text.indexof(listkey) + listkey.length());
run.settext(replace, 0);
}
}
}
// 清除最后一个)
if (i == paragraph.getruns().size() - 1) {
if (text.indexof(")") != -1) {
if (text.length() >= 1) {
string lasttext = run.gettext(0);
string replace = lasttext.replace(")", "");
run.settext(replace, 0);
}
}
}
}
}
}
private void changevalues(xwpfparagraph paragraph, xwpfrun currentrun, string currenttext, list<integer> runindex, map<string, object> map) throws exception {
object obj = getrealvalue(currenttext, map);
if (obj instanceof imageentity) {
currentrun.settext("", 0);
excelmapparse.addanimage((imageentity) obj, currentrun);
} else {
currenttext = obj.tostring();
poipublicutil.setwordtext(currentrun, currenttext);
}
for (int k = 0; k < runindex.size(); ++k) {
((xwpfrun) paragraph.getruns().get((integer) runindex.get(k))).settext("", 0);
}
runindex.clear();
}
public static object getrealvalue(string currenttext, map<string, object> map) throws exception {
string params = "";
while (currenttext.indexof("[") != -1) {
params = currenttext.substring(currenttext.indexof("[") + 1, currenttext.indexof("]"));
object obj = poielutil.eval(params.trim(), map);
if (obj instanceof imageentity || obj instanceof list || obj instanceof excellistentity) {
return obj;
}
if (obj != null) {
currenttext = currenttext.replace("[" + params + "]", obj.tostring());
} else {
currenttext = currenttext.replace("[" + params + "]", "");
}
}
return currenttext;
}
/**
* 复制表格段落
*
* @param source 原段落
* @param cell
*/
public xwpfparagraph copytableparagraph(xwpfparagraph source, xwpftablecell cell) {
// 使用游标创建一个新行
xmlcursor cursor = source.getctp().newcursor();
// 在游标位置插入新段落
xwpfparagraph newparagraph = cell.insertnewparagraph(cursor);
// 复制底层 xml 对象
xmlobject copy = source.getctp().copy();
newparagraph.getctp().set(copy);
// 关闭游标
cursor.dispose();
return newparagraph;
}
/**
* 表格段落复制
*
* @param document
* @param data
*/
private void createtableparagraph(xwpfdocument document, map<string, object> data) {
object resultlist = data.get("resultlist");
if (objects.isnull(resultlist)) {
return;
}
list list = (list) resultlist;
list<xwpftable> tables = document.gettables();
for (xwpftable table : tables) {
for (xwpftablerow row : table.getrows()) {
for (xwpftablecell cell : row.gettablecells()) {
xwpfparagraph xwpfparagraph = null;
list<xwpfparagraph> paragraphs = cell.getparagraphs();
for (int i = 0; i < paragraphs.size(); i++) {
xwpfparagraph paragraph = paragraphs.get(i);
string listname = paragraph.gettext().trim();
if (listname.startswith("($fe")) {
xwpfparagraph = paragraph;
}
}
if (objects.nonnull(xwpfparagraph)) {
for (int i = 0; i < list.size() - 1; i++) {
copytableparagraph(xwpfparagraph, cell);
}
}
}
}
}
}
/**
* 表格段落赋值
*
* @param document
* @param date
*/
private void evaltableparagraph(xwpfdocument document, map<string, object> date) {
list<xwpftable> tables = document.gettables();
for (xwpftable table : tables) {
for (xwpftablerow row : table.getrows()) {
for (xwpftablecell cell : row.gettablecells()) {
list<xwpfparagraph> paragraphs = cell.getparagraphs();
list<xwpfparagraph> addlist = new arraylist<>();
for (int i = 0; i < paragraphs.size(); i++) {
xwpfparagraph paragraph = paragraphs.get(i);
string listname = paragraph.gettext().trim();
if (listname.startswith("($fe")) {
addlist.add(paragraph);
}
}
try {
evalallparagraphic(addlist, date);
} catch (exception e) {
logger.error("单元格{}段落赋值异常", cell.gettext(), e);
}
}
}
}
}
}
具体的调用逻辑是先执行一次常规占位符的替换,然后再进行段落的替换。wordparagraphholder 中也是先生成段落,再进行段落赋值。
代码如下
public static file exportword(map<string, object> params, file templatefile, file writefile) {
fileoutputstream fos = null;
fileoutputstream outputstream = null;
try {
long start = system.currenttimemillis();
logger.info("开始写入word文件");
//获取模板文档
logger.info("开始写入word模板文件路径:{}", templatefile.getpath());
// 写入word常规替换
xwpfdocument doc = wordexportutil.exportword07(templatefile.getpath(), params);
// 循环写段落
wordparagraphholder paragraphholder = new wordparagraphholder(doc, writefile.getpath(), params);
string targetfilename = paragraphholder.execute();
logger.info("写入word文件完成,耗时{}", (system.currenttimemillis() - start));
return new file(targetfilename);
} catch (exception e) {
logger.error("写入word文件异常:", e);
} finally {
ioutils.closequietly(fos);
ioutils.closequietly(outputstream);
}
return null;
}
使用方法
新建word插入如下内容:
($fe:resultlist [createdate],我使用[number]元,购买苹果[amount]个,截止日期[enddate],使用支付方式[type])

其中resultlist 为入参list的名称,可以自由更换但是要和代码里的对应上,因为我们要生成多个段落,所以参数要构造为list的形式。
**[createdate]**内createdate为list中实体的字段名称,其他的为固定格式,不要修改,生成完成后会自动删除多余的字符串。
执行下面的测试代码:
public static void main(string[] args) throws exception {
map<string, object> date = createdate();
string sourcefile = "d:/temp/模版word.docx";
string targetfile = "d:/temp/输出结果.docx";
myxwpfdocument first = wordcache.getxwpfdocument(sourcefile);
wordparagraphholder test = new wordparagraphholder(first, targetfile, date);
test.execute();
}
private static map<string, object> createdate() {
//填充数据
list<wordexportbatch> resultlist = new arraylist<>();
wordexportbatch wordexport = new wordexportbatch();
wordexportbatch wordexport1 = new wordexportbatch();
wordexport.setcreatedate("2022/9/30");
wordexport1.setcreatedate("2022/9/28");
wordexport.setnumber("11");
wordexport1.setnumber("15");
wordexport.setamount("1234.5");
wordexport1.setamount("2345.77");
wordexport.setenddate("2022/12/31");
wordexport1.setenddate("2022/11/30");
wordexport.settype("支付宝");
wordexport1.settype("微信");
resultlist.add(wordexport);
resultlist.add(wordexport1);
//准备数据
map<string, object> params = new hashmap<>();
params.put("resultlist", resultlist);
return params;
}
看到输出如下就大功告成了。

补充pom依赖
有时候项目里poi的版本与easypoi李的poi会冲突,需要解决一下冲突。
<dependency> <groupid>cn.afterturn</groupid> <artifactid>easypoi-base</artifactid> <version>4.1.3</version> </dependency> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi-ooxml-schemas</artifactid> <version>4.1.2</version> </dependency> <dependency> <groupid>commons-io</groupid> <artifactid>commons-io</artifactid> <version>2.11.0</version> </dependency> <dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-lang3</artifactid> </dependency> <dependency> <groupid>cn.hutool</groupid> <artifactid>hutool-all</artifactid> <version>5.7.20</version> </dependency> <dependency> <groupid>commons-io</groupid> <artifactid>commons-io</artifactid> <version>2.16.1</version> </dependency>
word表格里段落复制
奇怪的需求千千万,遇到了需要在单元格内进行段落的循环复制。需要在生产段落的基础上做一下调整。增加创建表格段落的方法,并修改execute()执行方法。
public string execute() throws exception {
myxwpfdocument second = null;
fileoutputstream fos2 = null;
try {
map<string, object> data = this.data;
// 循环生成段落
this.parseallparagraphic(sourcedocument.getparagraphs(), data);
// 循环生成表格段落
this.createtableparagraph(sourcedocument, data);
file targetfile = new file(targetfilename);
fileoutputstream fos = fileutils.openoutputstream(targetfile);
sourcedocument.write(fos);
// 关闭流
fos.close();
sourcedocument.close();
second = wordcache.getxwpfdocument(targetfile.getpath());
// 循环段落赋值
this.evalallparagraphic(second.getparagraphs(), data);
// 循环表格段落赋值
this.evaltableparagraph(second, data);
fos2 = fileutils.openoutputstream(targetfile);
second.write(fos2);
} catch (exception e) {
logger.error("生成word段落失败:", e);
return "";
} finally {
// 关闭流
fos2.close();
second.close();
}
return targetfilename;
}
/**
* 复制表格段落
*
* @param source 原段落
* @param cell
*/
public xwpfparagraph copytableparagraph(xwpfparagraph source, xwpftablecell cell) {
// 使用游标创建一个新行
xmlcursor cursor = source.getctp().newcursor();
// 在游标位置插入新段落
xwpfparagraph newparagraph = cell.insertnewparagraph(cursor);
// 复制底层 xml 对象
xmlobject copy = source.getctp().copy();
newparagraph.getctp().set(copy);
// 关闭游标
cursor.dispose();
return newparagraph;
}
/**
* 表格段落复制
*
* @param document
* @param data
*/
private void createtableparagraph(xwpfdocument document, map<string, object> data) {
object resultlist = data.get("resultlist");
if (objects.isnull(resultlist)) {
return;
}
list list = (list) resultlist;
list<xwpftable> tables = document.gettables();
for (xwpftable table : tables) {
for (xwpftablerow row : table.getrows()) {
for (xwpftablecell cell : row.gettablecells()) {
xwpfparagraph xwpfparagraph = null;
list<xwpfparagraph> paragraphs = cell.getparagraphs();
for (int i = 0; i < paragraphs.size(); i++) {
xwpfparagraph paragraph = paragraphs.get(i);
string listname = paragraph.gettext().trim();
if (listname.startswith("($fe")) {
xwpfparagraph = paragraph;
}
}
if (objects.nonnull(xwpfparagraph)) {
for (int i = 0; i < list.size() - 1; i++) {
copytableparagraph(xwpfparagraph, cell);
}
}
}
}
}
}
/**
* 表格段落赋值
*
* @param document
* @param date
*/
private void evaltableparagraph(xwpfdocument document, map<string, object> date) {
list<xwpftable> tables = document.gettables();
for (xwpftable table : tables) {
for (xwpftablerow row : table.getrows()) {
for (xwpftablecell cell : row.gettablecells()) {
list<xwpfparagraph> paragraphs = cell.getparagraphs();
list<xwpfparagraph> addlist = new arraylist<>();
for (int i = 0; i < paragraphs.size(); i++) {
xwpfparagraph paragraph = paragraphs.get(i);
string listname = paragraph.gettext().trim();
if (listname.startswith("($fe")) {
addlist.add(paragraph);
}
}
try {
evalallparagraphic(addlist, date);
} catch (exception e) {
logger.error("单元格{}段落赋值异常", cell.gettext(), e);
}
}
}
}
}
public static void main(string[] args) throws exception {
map<string, object> date = createdate();
string sourcefile = "d:/temp/模版word1.docx";
string targetfile = "d:/temp/输出结果1.docx";
myxwpfdocument first = wordcache.getxwpfdocument(sourcefile);
wordparagraphholder1 test = new wordparagraphholder1(first, targetfile, date);
test.execute();
}
private static map<string, object> createdate() {
//填充数据
list<wordexportbatch> resultlist = new arraylist<>();
wordexportbatch wordexport = new wordexportbatch();
wordexportbatch wordexport1 = new wordexportbatch();
wordexport.setcreatedate("2022/9/30");
wordexport1.setcreatedate("2022/9/28");
wordexport.setnumber("11");
wordexport1.setnumber("15");
wordexport.setamount("1234.5");
wordexport1.setamount("2345.77");
wordexport.setenddate("2022/12/31");
wordexport1.setenddate("2022/11/30");
wordexport.settype("支付宝");
wordexport1.settype("微信");
resultlist.add(wordexport);
resultlist.add(wordexport1);
//准备数据
map<string, object> params = new hashmap<>();
params.put("resultlist", resultlist);
return params;
}($fe:resultlist [createdate],我使用[number]元,购买苹果[amount]个,截止日期[enddate],使用支付方式[type])
在word中新建表格,在对应的单元格里写入上面需要循环的内容。执行测试代码就可以看到输出结果。
以上就是使用easypoi实现word文档生成和段落循环的详细内容,更多关于easypoi word操作的资料请关注代码网其它相关文章!
发表评论