前一篇文章介绍了后台将前台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内容(&nbsp;需要被替换,否则导出会报错)
        String unescaped ="<!DOCTYPE html><html><head><title>导出word</title></head><body>" + body.replaceAll("&nbsp;","    ")
+"</body></html>";
if (unescaped.contains("&lt;/") ) {
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内容

java web实现在线编辑word,并将word导出(二)的更多相关文章

  1. java web实现在线编辑word,并将word导出(一)

    前段时间领导交代了一个需求:客户需要一个能够web在线编辑文字,如同编辑word文档一样,同时能够将编辑完成的内容导出为word文档并下载到本地. 我们选择了前台使用富文本插件的形式用于编辑内容,使用 ...

  2. java web实现在线编辑word,并将word导出(三)

    前面说到前台呈现的页面是img标签,因此需要在后台生成相应的图片,在img的src内容中改为相应的路径地址:而在生成文档的过程中需要替换相应的img标签.后一部分上篇文章已经讲过,本片主要讲前一部分. ...

  3. Java Web 常用在线api汇总(不定时更新)

    1.Hibernate API Documentation (3.2.2.ga) http://www.hibernate.org/hib_docs/v3/api/ 2.Spring Framewor ...

  4. java web 程序---在线时长

    思路:toLocalString()这个方法 <body> <% long t=session.getLastAccessedTime(); long t2=session.getC ...

  5. Java Web用Freemarker生成带图片的Word文档

    步骤一:模板制作 用world2003做一个导出模板,如果有图片则加入一张图片占位,将world另存为xml,将xml中需要导出的内容用Freemarker标签表示,最后另存为.ftl结尾的模板: 步 ...

  6. [原创]Java在线编辑word文档调用PageOffice实现并发控制

    1.功能介绍 PageOffice的并发控制功能用来解决多个用户在线编辑同一篇文档可能造成的互相覆盖修改结果的技术难题. B/S架构下用户访问都是并发的,也就是说经常会出现同时N个用户对一个服务器页面 ...

  7. Office word excel电子表格在线编辑的实现方法

    Office xp之后的版本支持通过webdav协议(http的扩展)直接编辑服务器上的文件. IIS(6.0)支持webdav,这在IIS管理器的web服务扩展中可以看到.利用IIS作为webdav ...

  8. 在线编辑word文档 可保存到服务器

    使用说明:该方法只在office xp 和 2003上 测试通过,2000及以下 版本没试. 注意:你要打开的服务器端的word文档要有写权限.iis要开起 web服务扩展中的webdav为允许 具体 ...

  9. java 网站源码 在线编辑模版 代码编辑器 兼容手机平板PC freemaker 静态引擎

    前台: 支持四套模版, 可以在后台切换   系统介绍: 1.网站后台采用主流的 SSM 框架 jsp JSTL,网站后台采用freemaker静态化模版引擎生成html 2.因为是生成的html,所以 ...

随机推荐

  1. 100、Java中String类之字符串转为大写

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  2. PHP几个快速读取大文件例子

    PHP几个快速读取大文件例子 感谢 把我给崩了 的投递 时间:2014-10-16 来源:三联 在PHP中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file.file_get_contents ...

  3. java与MySQL数据库的连接

    java与MySQL数据库的连接 1.数据库的安装和建立参见上一篇博客中的第1,2步骤.(http://blog.csdn.net/nuptboyzhb/article/details/8043091 ...

  4. TeX 常用命令记录

    常用: $a_{i}^{2}$    a_{i}^{2} $\sqrt x$     $\sqrt[n] x$     \sqrt[n] x $\frac{1+2}{3+4}$ 重音符号: $\hat ...

  5. Python下opencv使用笔记(图像频域滤波与傅里叶变换)

    Python下opencv使用笔记(图像频域滤波与傅里叶变换) 转载一只程序喵 最后发布于2018-04-06 19:07:26 阅读数 1654  收藏 展开 本文转载自  https://blog ...

  6. 第1节 IMPALA:4、5、linux磁盘的挂载和上传压缩包并解压

    第二步:开机之后进行磁盘挂载 分区,格式化,挂载新磁盘 磁盘挂载 df -lh fdisk -l 开始分区 fdisk /dev/sdb   这个命令执行后依次输 n  p  1  回车  回车  w ...

  7. tools.logback

    https://logback.qos.ch/manual/index.html Logback is built upon three main classes: Logger, Appender  ...

  8. synchronized wait notify 生产者消费者

    1.生产者消费者模型 public class ProducterConsumerTest{ public static void main(String[] args){ System.out.pr ...

  9. P1084 外观数列

    转跳点:

  10. 51nod 1423:最大二“货”

    1423 最大二"货" 题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题  收藏  取消关注 白克喜欢找一个序列 ...