最近有word转html功能的需求,收费第三方的不用,网上免费的poi 教程有的阉割了图片处理,有的版本太老,真是一步一个坑。最后都解决了,记录一下,贴出来解决办法,分享一下自己的工具类代码,示例,我所用到的jar包和版本。

如果有想直接word转html,并把图片转成base64一块存到网页中的可以直接看下这篇文章,我是把图片存到本地的:

使用POI实现word转html

另外说一下,word中的图片是在这个地方的

把word的后缀改为zip,并打开

java 将整个word转成一张图片 java poi word转图片_css


点进去里面的media,里面就是word中的所有图片

java 将整个word转成一张图片 java poi word转图片_java_02

说一下遇到的坑和介绍下解决方法

  1. word转html第三方服务收费。(解决办法:使用poi)
  2. poi转出的html内容有问题,比如序号1.变成%1。(解决办法:升级poi版本到4.1.2,低版本解析有问题,抱着试试看的想法升了下,解决了…)
  3. word转html图片处理(docx直接获取)
  4. doc格式图片不好获取。(解决办法:先存到本地(也可以直接存到想存的地方))
  5. (个人需求)doc格式返回到编辑器css格式全部丢失。(解决办法:使用Jsoup)
  6. 本地没问题,放到tomcat服务器提示各种类找不到,doc格式中文乱码。(解决办法:加jar包,乱码在代码中把格式utf-8改为gb2312)在文章结尾我分享下所有的jar包

简单说下第5个坑

docx解析出来的html是这样,css是内联样式

java 将整个word转成一张图片 java poi word转图片_css_03

而doc解析出来的html却是这样,css是内部样式,返回编辑器的html head被省去了,所以全部css样式都丢失了。

java 将整个word转成一张图片 java poi word转图片_html_04


下文在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>