java根据模板导出word
根据一个word模板,在程序中替换模板中的参数,然后根据这个模板导出word文件。
引入POI对word操作的依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
引入的版本一定要高一点,低版本会出现导出的word中图片无法显示的BUG
创建WordUtils类(使用poi导出word模板的操作都封装在这了)
@Component
public class WordUtils {
/**
* description:
* 导出word模板
* @param path:word模板路径
* @param params:模板中需要替换的参数可多个传递 比如 若想文字能够多行,在参数
* Map<String,Object>中的Object访如List<string>
* 若是传递图片 参数Map<String,Object>中的Object为Map<String,Obejct>
* 若是一个参数处要传入多张图片Object就为List<Map<String,Object>
* 在传入多张图片的时候 Map中设置style ,style=1 代表图片并排插入,
* style=2代表图片竖排插入,设置imgpath,即图片路径。
* 传递多张图片的时候imgpath和style两个参数必填,若是一张,imgpath必填
* @param filename:导出的word文件名
* @param response:
* @return void
*/
public void exportWord(String path, Map<String,Object> params, String filename, HttpServletResponse response) throws IOException, InvalidFormatException {
InputStream is=new FileInputStream(path);
XWPFDocument doc=new XWPFDocument(is);
iteraTable(params,doc);
OutputStream os=response.getOutputStream();
//设置导出的内容是doc
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + filename);
doc.write(os);
close(os);
}
//开始遍历文档中的表格
private void iteraTable(Map<String,Object> params,XWPFDocument doc) throws IOException, InvalidFormatException {
List<XWPFTableRow> rows=null;
List<XWPFTableCell> cells=null;
List<XWPFTable> tables=doc.getTables();
//遍历这个word文档的所有table表格
for (XWPFTable table :
tables) {
rows=table.getRows();
//遍历这个表格的所有行
for (XWPFTableRow row :
rows) {
cells=row.getTableCells();
//遍历这一行的单元格
for (XWPFTableCell cell:
cells) {
//判断该单元格的内容是否是字符串字段
if(strMatcher(cell.getText()).find()){
//替换字符串 字符串可以多行 也可以一行
replaceInStr(cell,params,doc);
continue;
}
//判断该单元格内容是否是需要替换的图片
if(imgMatcher(cell.getText()).find()){
//把模板中的内容替换成图片 图片可以多涨
replaceInImg(cell,params,doc);
continue;
}
}
}
}
}
//返回模板中图片字符串的匹配Matcher类
private Matcher imgMatcher(String imgstr){
Pattern pattern=Pattern.compile("@\\{(.+?)\\}");
Matcher matcher=pattern.matcher(imgstr);
return matcher;
}
//返回模板中变量的匹配Matcher类
private Matcher strMatcher(String str){
Pattern pattern=Pattern.compile("\\$\\{(.+?)\\}");
Matcher matcher=pattern.matcher(str);
return matcher;
}
//替换模板Table中相应字段为对应的字符串值
private void replaceInStr(XWPFTableCell cell, Map<String,Object> params, XWPFDocument doc) {
//两种数据类型 一种是直接String 还有一种是List<String>
String key = cell.getText().substring(2, cell.getText().length() - 1);
Integer datatype = getMapStrDataTypeValue(params, key);
List<XWPFParagraph> parags = cell.getParagraphs();
//先清空单元格中所有的段落
for (int i = 0; i < parags.size(); i++) {
cell.removeParagraph(i);
}
if (datatype.equals(0)) {
return;
} else if (datatype.equals(2)) {
//如果类型是2 说明数据类型是List<String>
List<String> strs = (List<String>) params.get(key);
Iterator<String> iterator = strs.iterator();
while (iterator.hasNext()) {
XWPFParagraph para = cell.addParagraph();
XWPFRun run = para.createRun();
run.setText(iterator.next());
}
} else if (datatype.equals(3)) {
String str = (String) params.get(key).toString();
cell.setText(str);
}
}
//替换模板Table中相应字段为图片
private void replaceInImg(XWPFTableCell cell,Map<String,Object> params,XWPFDocument doc) throws IOException, InvalidFormatException {
//拿参数 判断一下是什么类型 怎么处理 一般来说Map中只有两种类型 一种是一个模板图片变量放一张图片 只有一个Map 一种是一个模板变量中放多涨图片List<Map> 还有一种是不存在的情况
String key=cell.getText().substring(2,cell.getText().length()-1);
Integer datatype=getMapImgDataType(params,key);
List<XWPFParagraph> parags=cell.getParagraphs();
//先清空单元格中所有的段落
for (int i=0;i<parags.size();i++){
cell.removeParagraph(i);
}
if(datatype.equals(0)){
return;
}else if(datatype.equals(1)){
//处理单张图片
XWPFParagraph parag=cell.addParagraph();
Map<String,Object> pic=(Map<String,Object>)params.get(key);
insertImg(pic,parag);
}else if(datatype.equals(2)) {
//处理多涨图片
List<Map<String,Object>> pics=(List<Map<String,Object>>)params.get(key);
Iterator<Map<String, Object>> iterator = pics.iterator();
XWPFParagraph para = cell.addParagraph();
Integer count = 0;
while (iterator.hasNext()) {
Map<String, Object> pic = iterator.next();
//图片并排插入
if (Integer.parseInt(pic.get("style").toString()) == 1) {
//不做处理 这段代码可注释
}
//图片竖排插入
if (Integer.parseInt(pic.get("style").toString()) == 2) {
if (count > 0) {
para = cell.addParagraph();
}
}
insertImg(pic, para);
count++;
}
}
}
//插入图片 run创建
private void insertImg(Map<String,Object> pic,XWPFParagraph para) throws IOException, InvalidFormatException {
if(pic.get("imgpath").toString()==null)
return;
String picpath=pic.get("imgpath").toString();
InputStream is=null;
BufferedImage bi=null;
if(picpath.startsWith("http")) {
is=HttpUtils.getFileStream(picpath);
bi=ImageIO.read(HttpUtils.getFileStream(picpath));
}else {
is = new FileInputStream(picpath);
bi=ImageIO.read(new File(picpath));
}
XWPFRun run =para.createRun();
//原图片的长宽
Integer width=bi.getWidth();
Integer heigh=bi.getHeight();
Double much=80.0/width;
//图片按宽80 比例缩放
run.addPicture(is,getPictureType(picpath.substring(picpath.lastIndexOf(".")+1)),"", Units.toEMU(80),Units.toEMU(heigh*much));
//图片原长宽
// run.addPicture(is,getPictureType(pic.get("picType").toString()),"",Units.toEMU(width),Units.toEMU(heigh));
close(is);
bi=null;
}
//插入图片 自定义 doc方法
// private static void insertImg(Map<String,Object> pic,XWPFParagraph para,CustomXWPFDocument doc) throws FileNotFoundException, InvalidFormatException {
// InputStream is=null;
// if(pic.get("imgpath").toString()==null)
// return;
// is=new FileInputStream(pic.get("imgpath").toString());
// byte[] bytes=inputStream2ByteArray(is,true);
// doc.addPictureData(bytes, getPictureType(pic.get("picType").toString()));
// doc.createPicture(doc.getAllPictures().size() - 1, 30, 360,para);
// close(is);
// }
//处理模板中的变量名字,去掉${} 然后根据这个变量名在参数map中查找对应的Value值
private Integer getMapStrDataTypeValue(Map<String,Object> params,String key){
if(params.get(key)==null){
return 0;
}else if(params.get(key) instanceof List){
return 2;
}else if(params.get(key) instanceof String){
return 3;
}else {
throw new RuntimeException("Str data type error!");
}
}
//从params参数中找到模板中key对应的value值
private Integer getMapImgDataType(Map<String,Object> params,String key){
if(params.get(key)==null){
return 0;
}
else if(params.get(key) instanceof Map){
return 1;
}else if(params.get(key) instanceof List){
return 2;
}else {
throw new RuntimeException("image data type error!");
}
}
/**
* 根据图片类型,取得对应的图片类型代码
*
* @param picType
* @return int
*/
private int getPictureType(String picType) {
int res = XWPFDocument.PICTURE_TYPE_PICT;
if (picType != null) {
if (picType.equalsIgnoreCase("png")) {
res = XWPFDocument.PICTURE_TYPE_PNG;
} else if (picType.equalsIgnoreCase("dib")) {
res = XWPFDocument.PICTURE_TYPE_DIB;
} else if (picType.equalsIgnoreCase("emf")) {
res = XWPFDocument.PICTURE_TYPE_EMF;
} else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) {
res = XWPFDocument.PICTURE_TYPE_JPEG;
} else if (picType.equalsIgnoreCase("wmf")) {
res = XWPFDocument.PICTURE_TYPE_WMF;
}
}
return res;
}
/**
* 将输入流中的数据写入字节数组
*
* @param in
* @return
*/
public byte[] inputStream2ByteArray(InputStream in, boolean isClose) {
byte[] byteArray = null;
try {
//获得输入流中还有多少字节可读取
int total = in.available();
byteArray = new byte[total];
in.read(byteArray);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isClose) {
try {
in.close();
} catch (Exception e2) {
e2.getStackTrace();
}
}
}
return byteArray;
}
/**
* 关闭输入流
*
* @param is
*/
private void close(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭输出流
*
* @param os
*/
private void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
由于图片有时候并不是本地资源,而是别的服务器上的静态资源,所以创建一个HttpUtils类,写一个方法,通过图片的URL来获得图片的输入流。
HttpUtils.java
public class HttpUtils {
/**
* description:
* 通过图片URL地址得到文件流
* @Author any
* @Date 2020/8/14 11:51
* @param url:图片地址
* @return java.io.InputStream
*/
public static InputStream getFileStream(String url){
try {
URL httpUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection)httpUrl.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
InputStream inStream = conn.getInputStream();//通过输入流获取图片数据
return inStream;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
word模板
图片的前缀是@,如果是文字那么前缀是#,然后参数名用{}包裹起来。
模板示例
测试代码
@RestController
public class MainTest {
@Autowired
private WordUtils wordUtils;
@RequestMapping("/test/mydocx")
public void exportWord(HttpServletResponse response) throws IOException, InvalidFormatException {
//模板路径
String path="F:\\testmodel\\model.docx";
//传入模板的参数
Map<String,Object> params=new HashMap<>();
//模板的导出文件名字
String filename="mydocxtest.docx";
//一个单元格内一行字
params.put("name","张三");
params.put("sex","男");
//一个单元格内多行字
List<String> hobby=new ArrayList<>();
hobby.add("1、打篮球");
hobby.add("2、打羽毛球");
hobby.add("3、游泳");
params.put("hobby",hobby);
//图片参数1 多张图片 图片多行竖排排列 style为2
List<Map<String,Object>> imgs1List=new ArrayList<>();
Map<String,Object> img=new HashMap<>();
img.put("style",2);
img.put("imgpath","F:\\mytestimg\\testimg1.jpg");
imgs1List.add(img);
img=new HashMap<>();
img.put("style",2);
//放一张服务器上的图片资源
img.put("imgpath","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1597386736254&di=e25ddaabbe7e9259f00008f087cc8ab7&imgtype=0&src=http%3A%2F%2Fimg.mukewang.com%2Fszimg%2F5e3ae4bd096481b210320850.jpg");
imgs1List.add(img);
params.put("workimg",imgs1List);
//图片参数2 多张图片 一行内横排排列 style为1
List<Map<String,Object>> imgs2List=new ArrayList<>();
Map<String,Object> img2=new HashMap<>();
img2.put("style",1);
img2.put("imgpath","F:\\mytestimg\\sign1.png");
imgs2List.add(img2);
img2=new HashMap<>();
img2.put("style",1);
img2.put("imgpath","F:\\mytestimg\\sign2.png");
imgs2List.add(img2);
img2=new HashMap<>();
img2.put("style",1);
img2.put("imgpath","F:\\mytestimg\\sign3.png");
imgs2List.add(img2);
params.put("signimg",imgs2List);
//图片参数3 单张图片
Map<String,Object> img3=new HashMap<>();
img3.put("imgpath","F:\\mytestimg\\testimg1.jpg");
params.put("otherimg",img3);
wordUtils.exportWord(path,params,filename,response);
}
}
模板中的参数 一定要和程序中传入的参数的key值一样,比如模板参数#{name},那么程序中的map参数的key为name,这样就能对应起来了。
效果
备注
- 新建word后另存为Word 2003 XML文档(*.xml)类型即为模板文件,可以修改后缀为.ftl
- 模板备注后变量千万不要直接填写为${变量}的形式,模板会将其分割,可以先在word中设定变量,生成模板后再将变量改为${变量}的形式
- 项目部署需要提前将模板文件保存在服务器的项目路径的/templates下,有时间可修改此种方式不依赖于本地文件
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ALLBS!
评论