最近有word转html功能的需求,收费第三方的不用,网上免费的poi 教程有的阉割了图片处理,有的版本太老,真是一步一个坑。最后都解决了,记录一下,贴出来解决办法,分享一下自己的工具类代码,示例,我所用到的jar包和版本。
如果有想直接word转html,并把图片转成base64一块存到网页中的可以直接看下这篇文章,我是把图片存到本地的:
另外说一下,word中的图片是在这个地方的
把word的后缀改为zip,并打开
点进去里面的media,里面就是word中的所有图片
说一下遇到的坑和介绍下解决方法:
- word转html第三方服务收费。(解决办法:使用poi)
- poi转出的html内容有问题,比如序号1.变成%1。(解决办法:升级poi版本到4.1.2,低版本解析有问题,抱着试试看的想法升了下,解决了…)
- word转html图片处理(docx直接获取)
- doc格式图片不好获取。(解决办法:先存到本地(也可以直接存到想存的地方))
- (个人需求)doc格式返回到编辑器css格式全部丢失。(解决办法:使用Jsoup)
- 本地没问题,放到tomcat服务器提示各种类找不到,doc格式中文乱码。(解决办法:加jar包,乱码在代码中把格式utf-8改为gb2312)在文章结尾我分享下所有的jar包
简单说下第5个坑
docx解析出来的html是这样,css是内联样式。
而doc解析出来的html却是这样,css是内部样式,返回编辑器的html head被省去了,所以全部css样式都丢失了。
下文在JsoupUtils贴解决办法…
1 自己的处理步骤
1.1 总体是这样 (代码有删减,去掉了业务代码)
先获取html部分,然后把图片存到自己想存的地方,用Jsoup替换成自己存的图片路径。
public String docImport(MultipartFile file) throws Exception {
//返回的html字符串
String html = "";
//word类型是否doc
boolean wordType = false;
//临时图片存放文件夹 图片会保存在此路径(临时保存)(doc类型使用)
String docsTempImages = System.getProperty("java.io.tmpdir") + IdUtil.simpleUUID() + "/";
//判断类型
if (file.getOriginalFilename().endsWith(".docx") || file.getOriginalFilename().endsWith(".DOCX")) {
html = WordToHtmlUtil.Word2007ToHtml(file);
} else if (file.getOriginalFilename().endsWith(".doc") || file.getOriginalFilename().endsWith(".DOC")) {
wordType = true;
html = WordToHtmlUtil.Word2003ToHtmlAndSaveImage(docsTempImages, file);
}
//获取图片名称和本地url,这一步上传图片到本地,并把名字和url处理成map
String uploadPath = "";//word中的图片上传到哪
Map<String, String> imageMaps = WordToHtmlUtil.getImageMaps(uploadPath, docsTempImages, file);
//如果有图片替换成本地地址
if (!imageMaps.isEmpty()) {
html = this.replaceImgToLocal(html, imageMaps, wordType);
}
return html;
}
1.2 代码中的replaceImgToLocal方法
用Jsoup替换掉图片的链接。
doc格式也可以用Jsoup把内部样式转为内联样式
/**
* 替换html图片的路径
*
* @param html
* @param imageMaps
* @throws IOException
*/
private String replaceImgToLocal(String html, Map<String, String> imageMaps ,boolean wordType) {
String returnHtml = "";
//获取当前服务器ip和端口用于图片路径
Document doc = Jsoup.parse(html);
// 获取 带有src属性的img元素
Elements imgTags = doc.select("img[src]");
//替换图片
for (org.jsoup.nodes.Element element : imgTags) {
String imageName = StrUtil.subAfter(element.attr("src"), "/", true);
//根据名字获取map中的url,并覆盖之前存的word中的路径
element.attr("src", imageMaps.get(imageName));
}
returnHtml = doc.toString();
//doc格式样式在外部,所以要把style从外部移到内部
if (wordType) {
returnHtml = JsoupUtils.changeHtmlCssLineStyle(doc.toString());
}
return returnHtml;
}
1.3 工具类
工具类是根据我的需求写出来的,可以根据自己的要求改一下。
JsoupUtils
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import java.util.HashMap;
import java.util.Map;
/**
* 将html内部样式转为内联样式
*
* @auther: 胡辣汤麻辣烫
* @date: 2021/5/26
*/
public class JsoupUtils {
private static Map<String, String> getHtmlCss(String html) {
org.jsoup.nodes.Document doc = Jsoup.parse(html);
String[] styles = doc.head().select("style").html().split("\r\n");
Map<String, String> css = new HashMap<>();
for (String style : styles) {
String[] kv = style.split("\\{|\\}");
css.put(kv[0], kv[1]);
}
return css;
}
public static String changeHtmlCssLineStyle(String html) {
Map<String, String> css = getHtmlCss(html);
org.jsoup.nodes.Document doc = Jsoup.parse(html);
Element body = doc.body();
for (String key : css.keySet()) {
body.select(key).attr("style", css.get(key)).outerHtml();
}
return body.html();
}
}
1.4 WordToHtmlUtil
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.URLUtil;
import fr.opensagres.poi.xwpf.converter.xhtml.XHTMLConverter;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.converter.PicturesManager;
import org.apache.poi.hwpf.converter.WordToHtmlConverter;
import org.apache.poi.hwpf.usermodel.PictureType;
import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @auther: 胡辣汤麻辣烫
* @date: 2021/5/26
*/
public class WordToHtmlUtil {
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(WordToHtmlUtil.class);
/**
* 解析docx成html
*
* @param file
* @return
* @throws IOException
*/
public static String Word2007ToHtml(MultipartFile file) throws IOException {
if (file.isEmpty() || file.getSize() <= 0) {
logger.error("Sorry File does not Exists!");
return null;
} else {
if (file.getOriginalFilename().endsWith(".docx") || file.getOriginalFilename().endsWith(".DOCX")) {
// 1) 加载word文档生成 XWPFDocument对象
InputStream in = file.getInputStream();
XWPFDocument document = new XWPFDocument(in);
// 也可以使用字符数组流获取解析的内容
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XHTMLConverter.getInstance().convert(document, baos, null);
String content = baos.toString();
baos.close();
return content;
} else {
logger.error("Enter only MS Office 2007+ files");
return null;
}
}
}
/**
* 解析doc文章成html 不存图片
*
* @param file
* @return
* @throws IOException
* @throws ParserConfigurationException
* @throws TransformerException
*/
public static String Word2003ToHtml(MultipartFile file)
throws IOException, ParserConfigurationException, TransformerException {
if (file.isEmpty() || file.getSize() <= 0) {
logger.error("Sorry File does not Exists!");
return null;
} else {
if (file.getOriginalFilename().endsWith(".doc") || file.getOriginalFilename().endsWith(".DOC")) {
InputStream input = file.getInputStream();
HWPFDocument wordDocument = new HWPFDocument(input);
WordToHtmlConverter wordToHtmlConverter = new WordToHtmlConverter(
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
// 解析word文档
wordToHtmlConverter.processDocument(wordDocument);
Document htmlDocument = wordToHtmlConverter.getDocument();
// 也可以使用字符数组流获取解析的内容
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(baos);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer serializer = factory.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
// 也可以使用字符数组流获取解析的内容
String content = new String(baos.toByteArray());
baos.close();
return content;
} else {
logger.error("Enter only MS Office 2003 files");
return null;
}
}
}
/**
* 解析doc成html 并保存图片文件到本地
*
* @param file
* @return
* @throws IOException
* @throws ParserConfigurationException
* @throws TransformerException
*/
public static String Word2003ToHtmlAndSaveImage(String docsTempImages, MultipartFile file)
throws IOException, ParserConfigurationException, TransformerException {
if (file.isEmpty() || file.getSize() <= 0) {
logger.error("Sorry File does not Exists!");
return null;
} else {
if (file.getOriginalFilename().endsWith(".doc") || file.getOriginalFilename().endsWith(".DOC")) {
HWPFDocument wordDocument = new HWPFDocument(file.getInputStream());
WordToHtmlConverter wordToHtmlConverter = new WordToHtmlConverter(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
//设置图片存放的位置
wordToHtmlConverter.setPicturesManager(new PicturesManager() {
public String savePicture(byte[] content, PictureType pictureType, String suggestedName, float widthInches, float heightInches) {
File imgPath = new File(docsTempImages);
if (!imgPath.exists()) {//图片目录不存在则创建
imgPath.mkdirs();
}
File file = new File(docsTempImages + suggestedName);
try {
OutputStream os = new FileOutputStream(file);
os.write(content);
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return docsTempImages + suggestedName;
}
});
//解析word文档
wordToHtmlConverter.processDocument(wordDocument);
Document document = wordToHtmlConverter.getDocument();
// 也可以使用字符数组流获取解析的内容
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DOMSource domSource = new DOMSource(document);
StreamResult streamResult = new StreamResult(baos);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer serializer = factory.newTransformer();
// serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.ENCODING, "gb2312");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
baos.close();
// 也可以使用字符数组流获取解析的内容
return new String(baos.toByteArray());
} else {
logger.error("Enter only MS Office 2003 files");
return null;
}
}
}
/**
* 获取word中的图片名称和本地url(doc或docx)
* 返回map<图片名称, 存储的图片url地址>
*
* @param uploadPath 图片存放路径
* @param docsTempImages 本地临时图片存放地址(这个工具类Word2003ToHtmlAndSaveImage的方法存到了系统临时文件夹里)
* @param file
* @return
* @throws IOException
*/
public static Map<String, String> getImageMaps(String uploadPath, String docsTempImages, MultipartFile file) throws IOException {
//返回map
HashMap<String, String> map = new HashMap<>();
if (file.getOriginalFilename().endsWith(".docx") || file.getOriginalFilename().endsWith(".DOCX")) {
//获取存在word里的图片文件
InputStream in = file.getInputStream();
XWPFDocument document = new XWPFDocument(in);
List<XWPFParagraph> paragraphs = document.getParagraphs();
if (CollUtil.isNotEmpty(paragraphs)) {
paragraphs.forEach(p -> {
List<XWPFRun> runs = p.getRuns();
if (CollUtil.isNotEmpty(runs)) {
runs.forEach(r -> {
List<XWPFPicture> pictures = r.getEmbeddedPictures();
if (CollUtil.isNotEmpty(pictures)) {
pictures.forEach(c -> {
//这里找到word中的图片的名字和数据
XWPFPictureData pictureData = c.getPictureData();
String fileName = pictureData.getFileName();
byte[] data = pictureData.getData();
//保存到本地获取url
String localUrl = saveImageToLocalWithByte(fileName, data, uploadPath);
map.put(pictureData.getFileName(), localUrl);
});
}
});
}
});
}
} else if (file.getOriginalFilename().endsWith(".doc") || file.getOriginalFilename().endsWith(".DOC")) {
try {
File dir = new File(docsTempImages);
//如果目录不为空遍历存储到项目中
if (!FileUtil.isEmpty(dir)) {
Arrays.asList(FileUtil.ls(docsTempImages)).forEach(f -> {
String name = f.getName();
BufferedInputStream inputStream = FileUtil.getInputStream(f);
String localUrl = saveImageToLocalWithStream(name, inputStream, uploadPath);
map.put(name, localUrl);
});
}
} finally {
//删除临时文件夹
FileUtil.del(docsTempImages);
}
}
return map;
}
/**
* 保存图片到项目中,返回路径(byte[])
*
* @param name 图片名字
* @param data 图片字节数组
* @param uploadPath 存储路径
* @return
*/
private static String saveImageToLocalWithByte(String name, byte[] data, String uploadPath) {
FileUtil.writeBytes(data, uploadPath + name);
//自己项目的ip和端口,html图片地址要用,或者根据自己需求指定存到什么地方,自定义
String ipAndPort = "";
return URLUtil.normalize(ipAndPort + name);
}
/**
* 保存图片到项目中,返回路径(inputStream)
*
* @param name 图片名字
* @param inputStream 输入流
* @param uploadPath 存储路径
* @return
*/
private static String saveImageToLocalWithStream(String name, InputStream inputStream, String uploadPath) {
savePic(uploadPath, inputStream, name);
//自己项目的ip和端口,html图片地址要用,或者根据自己需求指定存到什么地方,自定义
String ipAndPort = "";
return URLUtil.normalize(ipAndPort + name);
}
/**
* 保存图片
*
* @param path 存储路径
* @param inputStream 输入流
* @param fileName 文件名称
*/
private static void savePic(String path, InputStream inputStream, String fileName) {
OutputStream os = null;
try {
// 2、保存到临时文件
// 1K的数据缓冲
byte[] bs = new byte[1024];
// 读取到的数据长度
int len;
// 输出的文件流保存到本地文件
File tempFile = new File(path);
if (!tempFile.exists()) {
tempFile.mkdirs();
}
os = new FileOutputStream(tempFile.getPath() + File.separator + fileName);
// 开始读取
while ((len = inputStream.read(bs)) != -1) {
os.write(bs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 完毕,关闭所有链接
try {
os.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2 用到的依赖的maven地址
<!-- 针对2007以上版本的库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 针对2003版本的库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.xhtml</artifactId>
<version>2.0.2</version>
</dependency>
<!-- jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
</dependency>
<!-- hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.2</version>
</dependency>
3 最后分享下部署到tomcat,提示各种类找不到,我最后找到的所有的jar包。
网盘:
链接:https://pan.baidu.com/s/16BHRiUn_Wshv7pJBfu9egg
提取码:gdd9