简介
Apache POI 是Java 领域中可以操作World,Excel,PPT文件的类库,可以用于生成报表,数据处理等。注意:Apache POI 从4.0.1版本开始,需要JDK 8 或更高版本支持。
依赖导入
<!--poi 类 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<!--word工具类-->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.9.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
<!--html渲染插件 注意0.4.2版本需要匹配jsoup的1.15.4版本-->
<dependency>
<groupId>io.github.draco1023</groupId>
<artifactId>poi-tl-ext</artifactId>
<version>0.4.2</version>
<exclusions>
<exclusion>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.4</version>
</dependency>
<!--样式依赖-->
<dependency>
<groupId>org.w3c.css</groupId>
<artifactId>sac</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>net.sourceforge.cssparser</groupId>
<artifactId>cssparser</artifactId>
<version>0.9.29</version>
</dependency>
富文本导出
在导出内容中包含有html标签的富文本,或者导出的数据是动态的可以组合为html标签富文本格式导出。以下是html渲染代码。
try {
// 富文本内容,这里读取的本地文件(文件内容可自定义,只要是html标签格式富文本都可以),文件见后面
String filePath = "D:\\html.txt";
FileInputStream fin = new FileInputStream(filePath);
InputStreamReader reader = new InputStreamReader(fin);
BufferedReader buffReader = new BufferedReader(reader);
StringBuilder richTextHtml = new StringBuilder();
String strTmp = "";
while ((strTmp = buffReader.readLine()) != null) {
richTextHtml.append(strTmp);
}
buffReader.close();
// HTML内容
String richTextHtmlString = richTextHtml.toString();
// html渲染插件
HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
Configure configure = Configure.builder()
// 注册html解析插件,只有注册的key才会解析html富文本。
.bind("content", htmlRenderPolicy)
.build();
// 映射数据Map
Map<String, Object> data = new HashMap<>();
data.put("topic","标题");
data.put("content", content2Html(richTextHtmlString));
// 读取模板文件,并渲染数据。test_template.docx模板文件 这里放在resources下面,模板内容可以自定义,与映射字段对应上即可。
XWPFTemplate template = XWPFTemplate.compile(Objects.requireNonNull(getResourceInputStream("/test_template.docx")), configure).render(data);
// 写入文件
template.writeToFile("D:\\test_template_return.docx");
template.close();
} catch (Exception e) {
e.printStackTrace();
}
样例代码中的富文本文件html.txt
<table>
<tr>
<td>html富文本</td>
</tr>
<tr>
<td>importance</td>
</tr>
<tr>
<td>html富文本</td>
</tr>
<tr>
<td>worry</td>
</tr>
</table>
样例代码中的模板文档test_template.docx,注意需要替换的内容映射参数使用{{}}阔起来。模板中ha还包含两张图片、空心饼图和柱状图在下一章用。
{{topic}}
{{content}}

图表和图片导出
主要思路是替换模板中已有的图表和图片,如果图表的数量不确定可以参考富文本的方式导出,这里主要介绍替换模板内容。
/**
* 程序入口
*/
public static void main(String[] args) throws Exception {
WordContentParserVO param = new WordContentParserVO();
// word模板路径
param.setPath("D:/test_template.docx");
// 饼图
List<WordChartParser.ChartReplace> chartReplaces = new ArrayList<>();
WordChartParser.ChartReplace chartReplace1 = new WordChartParser.ChartReplace();
chartReplace1.setIndex(1);
// 饼图-项
String[] categoryNames1 = new String[2];
categoryNames1[0]="通过率";
categoryNames1[1]="未通过率";
chartReplace1.setCategoryNames(categoryNames1);
// 饼图-值
List<WordChartParser.LineValue> lineValues1 = new ArrayList<>();
WordChartParser.LineValue lineValue1 = new WordChartParser.LineValue();
lineValue1.setLineName("饼图");
Number[] lineValueNumbers1 = new Number[2];
lineValueNumbers1[0]=85.5;
lineValueNumbers1[1]=14.5;
lineValue1.setLineValue(lineValueNumbers1);
lineValues1.add(lineValue1);
chartReplace1.setLineValues(lineValues1);
chartReplaces.add(chartReplace1);
// 柱状图
WordChartParser.ChartReplace chartReplace2 = new WordChartParser.ChartReplace();
chartReplace2.setIndex(2);
// 柱状图-项
String[] categoryNames2 = new String[2];
categoryNames2[0]="核心测评项";
categoryNames2[1]="一般测评项";
chartReplace2.setCategoryNames(categoryNames2);
// 柱状图-值
List<WordChartParser.LineValue> lineValues2 = new ArrayList<>();
WordChartParser.LineValue lineValue2 = new WordChartParser.LineValue();
lineValue2.setLineName("通过");
Number[] lineValueNumbers2 = new Number[2];
lineValueNumbers2[0]=60;
lineValueNumbers2[1]=61;
lineValue2.setLineValue(lineValueNumbers2);
lineValues2.add(lineValue2);
WordChartParser.LineValue lineValue3 = new WordChartParser.LineValue();
lineValue3.setLineName("未通过");
Number[] lineValueNumbers3 = new Number[2];
lineValueNumbers3[0]=20;
lineValueNumbers3[1]=21;
lineValue3.setLineValue(lineValueNumbers3);
lineValues2.add(lineValue3);
chartReplace2.setLineValues(lineValues2);
chartReplaces.add(chartReplace2);
param.setChartReplaces(chartReplaces);
// 模板图片替换
List<WordPictureParser.PictureReplace> pictureReplaceList = new ArrayList<>();
WordPictureParser.PictureReplace pictureReplace1=new WordPictureParser.PictureReplace();
pictureReplace1.setIndex(0);
pictureReplace1.setType(2);
// 需要替换成哪张图片 自己下载到本地的图片
pictureReplace1.setPictureData(getResourceInputStream("D:/img1.png"));
pictureReplaceList.add(pictureReplace1);
WordPictureParser.PictureReplace pictureReplace2=new WordPictureParser.PictureReplace();
pictureReplace2.setIndex(1);
pictureReplace2.setType(2);
// 需要替换成哪张图片 自己下载到本地的图片
pictureReplace2.setPictureData(getResourceInputStream("D:/img2.png"));
pictureReplaceList.add(pictureReplace2);
param.setPictureReplaces(pictureReplaceList);
// 模板渲染
WordContentParser parser = PoiUtil.replaceWordContent(param);
File file = parser.outputFile("D:/test_result.docx");
System.out.println("word文件替换成功,保存路径:"+file.getAbsolutePath());
}
/**
* 获取资源文件的文件流
*
* @return InputStream
*/
public static InputStream getResourceInputStream(String filePath) {
File file = new File(filePath);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
return fileInputStream;
}
/**
* word模板图片、图表内容替换工具类
*
*/
public class PoiUtil {
public static WordContentParser replaceWordContent(WordContentParserVO param) {
XWPFDocument doc = WordContentParser.loadXWPFDocument(param.getPath());
//图表替换
WordChartParser chartParser = new WordChartParser(doc, param.getChartReplaces());
chartParser.replace();
//图片替换
WordPictureParser pictureParser = new WordPictureParser(doc, param.getPictureReplaces());
pictureParser.replace();
return chartParser;
}
}
@Data
public class WordContentParserVO {
private String path;
private List<WordChartParser.ChartReplace> chartReplaces;
private List<WordPictureParser.PictureReplace> pictureReplaces;
}
/**
* Word图表解析器
*/
@Setter
@Getter
public class WordChartParser extends WordContentParser {
private List<ChartReplace> chartReplaces;
public WordChartParser(XWPFDocument doc, List<ChartReplace> chartReplaces) {
super(doc);
this.chartReplaces = chartReplaces;
}
@Setter
@Getter
public static class ChartReplace {
//图表标题
private String chartTitle;
//x分类轴标题
private String categoryAxisTitle;
//y值轴标题
private String valueAxesTitle;
//图表在模板中的下标,从1开始
private int index;
//图表分类名称
private String[] categoryNames;
//图表数据行值
private List<LineValue> lineValues;
}
@Setter
@Getter
public static class LineValue {
//图表数据名称
private String lineName;
//图表数据行值
private Number[] lineValue;
//设置标记样式
private MarkerStyle markerStyle;
public LineValue() {
// 设置标记样式
markerStyle = MarkerStyle.NONE;
}
}
@Override
public XWPFDocument replace() {
if (Objects.isNull(getDoc())) {
throw new RuntimeException(ERR_MSG_DOC_NOT_LOADED);
}
if (CollectionUtils.isEmpty(chartReplaces)) {
throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
}
List<XWPFChart> charts = getDoc().getCharts();
if (CollectionUtils.isEmpty(charts)) {
throw new RuntimeException(ERR_MSG_TEMPLATE_CHART_NOT_EXIST.replace("${name}", ""));
}
Map<Integer, XWPFChart> templateChartMap = new HashMap<>();
for (int i = 1, size = charts.size(); i <= size; i++) {
String key = charts.get(i - 1).toString().replaceAll("Name: ", "")
.replaceAll(" - Content Type: application/vnd\\.openxmlformats-officedocument\\.drawingml\\.chart\\+xml", "").trim();
key = key.replace("/word/charts/chart", "").replace(".xml", "");
templateChartMap.put(Integer.valueOf(key), charts.get(i - 1));
}
chartReplaces.forEach(lineChartReplace -> {
if (CollectionUtils.isEmpty(lineChartReplace.getLineValues())) {
throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
}
//校验元素个数是否一致
int dataLength = lineChartReplace.getCategoryNames().length;
for (int i = 0, size = lineChartReplace.getLineValues().size(); i < size; i++) {
if (lineChartReplace.getLineValues().get(i).getLineValue().length != dataLength) {
throw new RuntimeException(ERR_MSG_ROW_ELEMENT_MISMATCH.replace("${index}", String.valueOf(i)));
}
}
//校验图表是否存在
XWPFChart templateChart = templateChartMap.get(lineChartReplace.getIndex());
if (Objects.isNull(templateChart)) {
throw new RuntimeException(ERR_MSG_TEMPLATE_CHART_NOT_EXIST.replace("${index}", String.valueOf(lineChartReplace.getIndex())));
}
});
//逐个图表进行内容替换
chartReplaces.forEach(lineChartReplace -> {
XWPFChart templateChart = templateChartMap.get(lineChartReplace.getIndex());
templateChart.setTitleText(lineChartReplace.getChartTitle());//图表标题
XDDFCategoryDataSource categoryDataSource = XDDFDataSourcesFactory.fromArray(lineChartReplace.getCategoryNames());//分类名称
templateChart.getChartSeries().forEach(chartData -> {
if (Objects.nonNull(chartData.getCategoryAxis())) {
chartData.getCategoryAxis().setTitle(lineChartReplace.getCategoryAxisTitle());//x轴标题
}
if (Objects.nonNull(chartData.getValueAxes())) {
chartData.getValueAxes().forEach(axis -> {
axis.setTitle(lineChartReplace.getValueAxesTitle());//y轴标题
});
}
//替换模板自带的图表数据
for (int i = 0, count = chartData.getSeriesCount(); i < count; i++) {
XDDFNumericalDataSource<Number> values = XDDFDataSourcesFactory.fromArray(lineChartReplace.getLineValues().get(i).getLineValue());
XDDFChartData.Series series = chartData.getSeries(i);
series.replaceData(categoryDataSource, values);
}
// 绘制
templateChart.plot(chartData);
});
});
return getDoc();
}
}
/**
* Word内容解析器
*/
@Setter
@Getter
@Slf4j
public abstract class WordContentParser {
public static final String ERR_MSG_DOC_LOAD_FAIL = "文档加载失败";
public static final String ERR_MSG_DOC_OUTPUT_FAIL = "文档导出失败";
public static final String ERR_MSG_DOC_NOT_LOADED = "文档未加载";
public static final String ERR_MSG_PARAM_NOT_INITIALIZED = "替换参数未初始化";
public static final String ERR_MSG_ROW_ELEMENT_MISMATCH = "下标${index}的行值元素不匹配";
public static final String ERR_MSG_TABLE_NOT_EXIST = "不存在下标${index}的表格";
public static final String ERR_MSG_CELLS_NOT_UNANIMOUS = "下标${index}的表格值和单元格个数不一致";
public static final String ERR_MSG_TEMPLATE_CHART_NOT_EXIST = "不存在下标${index}的图表";
public static final String ERR_MSG_PIC_NOT_ADD = "下标${index}的图片替换失败";
public static final String ERR_MSG_PIC_NOT_EXIST = "模板中${type}下标${index}的图片不存在替换值";
//文档对象
private XWPFDocument doc;
public WordContentParser(InputStream in) {
try {
this.doc = new XWPFDocument(in);
} catch (IOException e) {
log.error("{}:{}", ERR_MSG_DOC_LOAD_FAIL, e.getMessage());
throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
}
}
public WordContentParser(XWPFDocument doc) {
this.doc = doc;
}
public WordContentParser(String path) {
this.doc = loadXWPFDocument(path);
}
public static XWPFDocument loadXWPFDocument(String path) {
InputStream in = null;
XWPFDocument doc = null;
try {
in = new FileInputStream(path);
//in = new ClassPathResource(path).getInputStream(); spring-boot项目打成jar部署到Linux需要改成这个加载方式,path用相对路径
doc = new XWPFDocument(in);
} catch (Exception e) {
log.error("{}:{}", ERR_MSG_DOC_LOAD_FAIL, e.getMessage());
throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
} finally {
if (Objects.nonNull(in)) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return doc;
}
}
public File outputFile(String path) {
if (Objects.isNull(doc)) {
throw new RuntimeException(ERR_MSG_DOC_LOAD_FAIL);
}
File file = null;
FileOutputStream fos = null;
try {
file = new File(path);
if (file.exists()) {
file.delete();
}
fos = new FileOutputStream(path);
doc.write(fos);
} catch (Exception e) {
log.error("{}:{}", ERR_MSG_DOC_OUTPUT_FAIL, e.getMessage());
throw new RuntimeException(ERR_MSG_DOC_OUTPUT_FAIL);
} finally {
if (Objects.nonNull(fos)) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
}
public abstract XWPFDocument replace();
}
/**
* Word图片解析器
*
*/
@Setter
@Getter
@Slf4j
public class WordPictureParser extends WordContentParser {
//模板中${type}下标${index}的图片不存在替换值---是否抛异常
private boolean throwException;
private List<PictureReplace> pictureReplaces;
private int picIndexOfParagraph;
private void setPicIndexOfParagraph(int picIndexOfParagraph) {
this.picIndexOfParagraph = picIndexOfParagraph;
}
public WordPictureParser(InputStream in, List<PictureReplace> pictureReplaces) {
super(in);
this.pictureReplaces = pictureReplaces;
this.throwException = true;
this.picIndexOfParagraph = 0;
}
public WordPictureParser(XWPFDocument doc, List<PictureReplace> pictureReplaces) {
super(doc);
this.pictureReplaces = pictureReplaces;
this.throwException = true;
this.picIndexOfParagraph = 0;
}
public WordPictureParser(InputStream in) {
super(in);
this.throwException = true;
this.picIndexOfParagraph = 0;
}
public WordPictureParser(XWPFDocument doc) {
super(doc);
this.throwException = true;
this.picIndexOfParagraph = 0;
}
public WordPictureParser(String path) {
super(path);
this.throwException = true;
this.picIndexOfParagraph = 0;
}
@Setter
@Getter
public static class PictureReplace {
//内容类型,1 段落图片 、 2 表格图片
private int type;
public static final int TYPE_PARAGRAPH = 1;
public static final int TYPE_TABLE = 2;
//图片在模板的下标,Word文档段落和表格从左到右算起,段落和表格单独分开计数,0开始
private int index;
//图片流数据
private InputStream pictureData;
//图片类型
private int pictureType;
public PictureReplace() {
this.type = TYPE_PARAGRAPH;
this.pictureType = XWPFDocument.PICTURE_TYPE_PNG;
}
}
public static InputStream loadPictureData(String path) {
try {
return new FileInputStream(path);
} catch (FileNotFoundException e) {
log.error("FileNotFoundException->{}", path);
return null;
}
}
@Override
public XWPFDocument replace() {
if (Objects.isNull(getDoc())) {
throw new RuntimeException(ERR_MSG_DOC_NOT_LOADED);
}
if (CollectionUtils.isEmpty(pictureReplaces)) {
throw new RuntimeException(ERR_MSG_PARAM_NOT_INITIALIZED);
}
picIndexOfParagraph = 0;
pictureReplace(PictureReplace.TYPE_PARAGRAPH, getDoc().getParagraphs());
picIndexOfParagraph = 0;
getDoc().getTables().forEach(xwpfTable -> {
xwpfTable.getRows().forEach(row -> {
row.getTableCells().forEach(xwpfTableCell -> {
pictureReplace(PictureReplace.TYPE_TABLE, xwpfTableCell.getParagraphs());
});
});
});
pictureReplaces.forEach(pictureReplace -> {
if (Objects.nonNull(pictureReplace.getPictureData())) {
try {
pictureReplace.getPictureData().close();
} catch (IOException e) {
log.error("{} {}", pictureReplace, e.getMessage());
}
}
});
return getDoc();
}
private void pictureReplace(int type, List<XWPFParagraph> xwpfParagraphs) {
int runIndex = 0;
for (int i = 0, size = xwpfParagraphs.size(); i < size; i++) {
XWPFParagraph xwpfParagraph = xwpfParagraphs.get(i);
List<XWPFRun> runs = xwpfParagraph.getRuns();
for (int j = 0, size2 = runs.size(); j < size2; j++) {
List<XWPFPicture> xwpfPictures = runs.get(j).getEmbeddedPictures();
if (CollectionUtils.isNotEmpty(xwpfPictures)) {
PictureReplace pictureReplace = null;
runIndex = j;
XWPFRun newXWPFRun = xwpfParagraph.createRun();
for (int k = 0, size3 = xwpfPictures.size(); k < size3; k++) {
int width = (int) xwpfPictures.get(k).getCTPicture().getSpPr().getXfrm().getExt().getCx();
int height = (int) xwpfPictures.get(k).getCTPicture().getSpPr().getXfrm().getExt().getCy();
String filename = xwpfPictures.get(k).getPictureData().getFileName();
int finalPicIndexOfParagraph = picIndexOfParagraph;
pictureReplace = pictureReplaces.stream().filter(o -> type == o.getType()).filter(o -> o.getIndex() == finalPicIndexOfParagraph).findFirst().orElse(null);
if (Objects.nonNull(pictureReplace)) {
try {
newXWPFRun.addPicture(pictureReplace.getPictureData(), pictureReplace.getPictureType(), filename, width, height);
} catch (Exception e) {
log.error("{};{}", ERR_MSG_PIC_NOT_ADD.replace("${index}", String.valueOf(finalPicIndexOfParagraph)), e.getMessage());
throw new RuntimeException(ERR_MSG_PIC_NOT_ADD.replace("${index}", String.valueOf(finalPicIndexOfParagraph)));
}
} else {
if (throwException) {
throw new RuntimeException(ERR_MSG_PIC_NOT_EXIST.replace("${type}", type == PictureReplace.TYPE_PARAGRAPH ? "段落图片" : "表格图片").replace("${index}", String.valueOf(finalPicIndexOfParagraph)));
}
}
picIndexOfParagraph++;
}
if (Objects.nonNull(pictureReplace)) {
xwpfParagraph.removeRun(runIndex);
xwpfParagraph.addRun(newXWPFRun);
}
}
}
}
}
}
961





