前一篇文章介绍了后台将前台html转为word文档的一种方式,但却有一个问题是没法讲图片放置在生成的word报告中。我在网上找了很多方法,甚至将图片转换成base64编码的方式也不成功。效果如下:
由于本人的水平有限,因此使用其他的实现方式。
首先介绍一下前台呈现图片的原理:前台ueditor编辑框呈现的图片实际上是一个img变迁,呈现的图片的原始文件是存在服务器上的(甚至在udeitor中直接粘贴图片也是想服务器上传了该图片)。
对应服务器文件为:
因此,我的视线思路是先将页面传回后台的html内容中的img标签使用特定的字符替换,直接生成一个文本文档,然后在在对应img标签的位置替换为相应的图片。因为在看网上说不能直接操作doc文档插入图片,需要生成docx文档,于是我又参考网上实现了生成docx的后台代码。
这次生成word文档使用的是docx4j,插入图片需要引入poi的另一个依赖,所需依赖如下:
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>3.3.6</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
<version>3.3.6</version>
</dependency>
代码实现如下:
@RequestMapping("/defectV2/defect/analysis/tranformHtmlToWord")
@ResponseBody
public MessageBean tranformHtmlToWordDocx(@RequestParam Map params,HttpServletRequest request, HttpServletResponse response) {
try {
// params包含前台传回的html内容
analysisService.tranformHtmlToWordDocx(params,request,response);
return new MessageBean("success", "生成报告成功!", null);
} catch (Exception e) {
e.printStackTrace();
utils.WriteSystemLog(sls, "ERROR", "生成报告", "生成报告失败!" + e.getCause());
return new MessageBean("error", "生成报告失败!", null);
}
}
public String tranformHtmlToWordDocx(Map params, HttpServletRequest request, HttpServletResponse response) throws Exception {
String body = (String) params.get("editorValue");
List<String> imgList = new ArrayList<String>();
String imgTag = "";//img标签
String imgRegex = "<img[^>]+/>";
Pattern imgPattern = Pattern.compile(imgRegex,Pattern.CASE_INSENSITIVE);
Matcher imgMatcher = imgPattern.matcher(body);
int count = 1;
while (imgMatcher.find()){
imgTag = imgMatcher.group(); //获取img标签
String srcStr = "";
//获取匹配img标签中的src内容
Matcher srcMatcher = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)").matcher(imgTag);
while (srcMatcher.find()){
srcStr = srcMatcher.group(1);
}
String contextPath = request.getContextPath();
String imagePath = ExportWord.getRealPath() + srcStr.substring(srcStr.indexOf(contextPath) + contextPath.length() +1);
imgList.add(imagePath);
body = body.replace(imgTag,"${img" + count + "}");
count ++;
}
//html内容( 需要被替换,否则导出会报错)
String unescaped ="<!DOCTYPE html><html><head><title>导出word</title></head><body>" + body.replaceAll(" "," ")
+"</body></html>";
if (unescaped.contains("</") ) {
unescaped = StringEscapeUtils.unescapeHtml(unescaped);
}
// 创建一个空的docx对象
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
XHTMLImporter importer = new XHTMLImporterImpl(wordMLPackage);
importer.setTableFormatting(FormattingOption.IGNORE_CLASS);
importer.setParagraphFormatting(FormattingOption.IGNORE_CLASS);
NumberingDefinitionsPart ndp = new NumberingDefinitionsPart();
wordMLPackage.getMainDocumentPart().addTargetPart(ndp);
ndp.unmarshalDefaultNumbering();
// 转换XHTML,并将其添加到我们制作的空docx中
XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
XHTMLImporter.setHyperlinkStyle("Hyperlink");
wordMLPackage.getMainDocumentPart().getContent().addAll(
XHTMLImporter.convert(unescaped,null));
//ExportWord.getWordPath()是获取项目文件路径
String wordPath = ExportWord.getWordPath() + new Date().getTime() + "问题分析统计.docx";
wordMLPackage.save(new File(wordPath));
Map<String, Object> testMap = new HashMap<String, Object>();
WordUtils wordUtil=new WordUtils();
if (imgList!=null && imgList.size()>0){
for (int i=0;i<imgList.size();i++){
String imagePath = imgList.get(i);
Map map = new HashMap();
//封装的单个图片信息
map = WordUtils.packageImgMessage(imagePath);
testMap.put("${img" + (i+1) + "}",map);
}
}
wordUtil.getWord(wordPath,testMap,new ArrayList<String[]>(),"质量分析统计报告.docx",response);
return null;
}
WordUtil.java实现代码(参考了网上的)
public class WordUtils {
/**
* 根据模板生成word
* @param path 模板的路径
* @param params 需要替换的参数
* @param tableList 需要插入的参数
* @param fileName 生成word文件的文件名
* @param response
*/
public void getWord(String path, Map<String, Object> params, List<String[]> tableList, String fileName, HttpServletResponse response) throws Exception {
File file = new File(path);
InputStream is = new FileInputStream(file);
CustomXWPFDocument doc = new CustomXWPFDocument(is);
this.replaceInPara(doc, params); //替换文本里面的变量
this.replaceInTable(doc, params, tableList); //替换表格里面的变量
OutputStream os = response.getOutputStream();
response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName,"utf-8"));
doc.write(os);
this.close(os);
this.close(is);
}
/**
* 替换段落里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private void replaceInPara(CustomXWPFDocument doc, Map<String, Object> params) {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph para;
while (iterator.hasNext()) {
para = iterator.next();
this.replaceInPara(para, params, doc);
}
}
/**
* 替换段落里面的变量
*(网络上找到的)
* @param para 要替换的段落
* @param params 参数
*/
private void replaceInParaYuan(XWPFParagraph para, Map<String, Object> params, CustomXWPFDocument doc) {
List<XWPFRun> runs;
Matcher matcher;
if (this.matcher(para.getParagraphText()).find()) {
runs = para.getRuns();
int start = -1;
int end = -1;
String str = "";
for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
String runText = run.toString();
//找出“${”开始的XWPFRun
if ('$' == runText.charAt(0) && '{' == runText.charAt(1)) {
start = i;
}
//若有“${”,则记录开始的XWPFRun
if ((start != -1)) {
str += runText;
}
//若有“}”,记录结束的XWPFRun
if ('}' == runText.charAt(runText.length() - 1)) {
if (start != -1) {
end = i;
break;
}
}
}
//${变量}之间的XWPFRun
for (int i = start; i <= end; i++) {
para.removeRun(i);
i--;
end--;
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
String key = entry.getKey();
if (str.indexOf(key) != -1) {
Object value = entry.getValue();
if (value instanceof String) {
str = str.replace(key, value.toString());
para.createRun().setText(str, 0);
break;
} else if (value instanceof Map) {
str = str.replace(key, "");
Map pic = (Map) value;
int width = Integer.parseInt(pic.get("width").toString());
int height = Integer.parseInt(pic.get("height").toString());
int picType = getPictureType(pic.get("type").toString());
byte[] byteArray = (byte[]) pic.get("content");
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray);
try {
//int ind = doc.addPicture(byteInputStream,picType);
//doc.createPicture(ind, width , height,para);
doc.addPictureData(byteInputStream, picType);
doc.createPicture(doc.getAllPictures().size() - 1, width, height, para);
para.createRun().setText(str, 0);
break;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 替换段落里面的变量
* (优化后的)
* @param para 要替换的段落
* @param params 参数
*/
private void replaceInPara(XWPFParagraph para, Map<String, Object> params, CustomXWPFDocument doc) {
List<XWPFRun> runs;
Matcher matcher;
String paraText = para.getParagraphText();
if(matcher(paraText).find()){
runs = para.getRuns();
for (int i=0;i<runs.size();i++){
XWPFRun run = runs.get(i);
String runText = run.toString();
matcher = matcher(runText);
List<String> $StrList = new ArrayList<String>();
while (matcher.find()){
$StrList.add(matcher.group());
}
if ($StrList.size()>0){
// para.removeRun(i);
for (String $Str : $StrList){
if (params.containsKey($Str)){
Object value = params.get($Str);
if (value instanceof String) {
runText = runText.replace($Str, value.toString());
// para.createRun().setText(runText, 0);
// break;
} else if (value instanceof Map) {
runText = runText.replace($Str, "");
Map pic = (Map) value;
int width = Integer.parseInt(pic.get("width").toString());
int height = Integer.parseInt(pic.get("height").toString());
int picType = getPictureType(pic.get("type").toString());
byte[] byteArray = (byte[]) pic.get("content");
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray);
try {
//int ind = doc.addPicture(byteInputStream,picType);
//doc.createPicture(ind, width , height,para);
doc.addPictureData(byteInputStream, picType);
doc.createPicture(doc.getAllPictures().size() - 1, width, height, para);
// para.createRun().setText(runText, 0);
// break;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// para.createRun().setText(runText, 0);
run.setText(runText,0);
}
}
}
}
/**
* 为表格插入数据,行数不够添加新行
*
* @param table 需要插入数据的表格
* @param tableList 插入数据集合
*/
private static void insertTable(XWPFTable table, List<String[]> tableList) {
//创建行,根据需要插入的数据添加新行,不处理表头
for (int i = 0; i < tableList.size(); i++) {
XWPFTableRow row = table.createRow();
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
int length = table.getRows().size();
for (int i = 1; i < length - 1; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for (int j = 0; j < cells.size()&&tableList!=null&&tableList.size()>0; j++) {
XWPFTableCell cell = cells.get(j);
String s = tableList.get(i - 1)[j];
cell.setText(s);
}
}
}
/**
* 替换表格里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private void replaceInTable(CustomXWPFDocument doc, Map<String, Object> params, List<String[]> tableList) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
while (iterator.hasNext()) {
table = iterator.next();
if (table.getRows().size() > 1) {
//判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
if (this.matcher(table.getText()).find()) {
rows = table.getRows();
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
this.replaceInPara(para, params, doc);
}
}
}
} else {
insertTable(table, tableList); //插入数据
}
}
}
}
/**
* 正则匹配字符串
*
* @param str
* @return
*/
private Matcher matcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
return matcher;
}
/**
* 根据图片类型,取得对应的图片类型代码
*
* @param picType
* @return int
*/
private static int getPictureType(String picType) {
int res = CustomXWPFDocument.PICTURE_TYPE_PICT;
if (picType != null) {
if (picType.equalsIgnoreCase("png")) {
res = CustomXWPFDocument.PICTURE_TYPE_PNG;
} else if (picType.equalsIgnoreCase("dib")) {
res = CustomXWPFDocument.PICTURE_TYPE_DIB;
} else if (picType.equalsIgnoreCase("emf")) {
res = CustomXWPFDocument.PICTURE_TYPE_EMF;
} else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) {
res = CustomXWPFDocument.PICTURE_TYPE_JPEG;
} else if (picType.equalsIgnoreCase("wmf")) {
res = CustomXWPFDocument.PICTURE_TYPE_WMF;
}
}
return res;
}
// /**
// * 将输入流中的数据写入字节数组
// *
// * @param in
// * @return
// */
// public static 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;
// }
/**
* 封装图片信息
* @return
*/
public static Map packageImgMessage(String imagePath){
Map map = new HashMap();
InputStream ips = null;
try {
ips = new FileInputStream(imagePath);
BufferedImage image = ImageIO.read(ips);
map.put("width", image.getWidth());
map.put("height", image.getHeight());
ips.close();
ips = new FileInputStream(imagePath);
map.put("type", imagePath.substring(imagePath.lastIndexOf('.')+1));
int total = ips.available();
byte[] byteArray = new byte[total];
ips.read(byteArray);
map.put("content", byteArray);
} catch (Exception e) {
e.printStackTrace();
}finally {
if (ips != null){
try {
ips.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return map;
}
/**
* 关闭输入流
*
* @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();
}
}
}
}
这样就实现了前台html转换为docx的word文档,并且将前台呈现的图片插入到生成的文档中。
我想解决文档中的图片的初衷是因为导出的文档需要有饼图,如下所示:
这个饼图是word文档做出来的,并不是图片,我使用了简单的方法使用图片展现的方式呈现出来。下篇文章将介绍生成饼图的实现方式。
params包含前台传回的html内容