java web实现在线编辑word,并将word导出
Posted 基础不牢,地动山摇!保持每周至少三篇博文,质量会有些降低。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java web实现在线编辑word,并将word导出相关的知识,希望对你有一定的参考价值。
前一篇文章介绍了后台将前台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内容
以上是关于java web实现在线编辑word,并将word导出的主要内容,如果未能解决你的问题,请参考以下文章