利用FreeMarker将带指定格式的富文本导出为word并打包(仅含base64格式有效)

最近项目需要将知识库导出,导出为word,网上查了很多资料,但是总会遇到很多磕磕绊绊,花了很多时间。希望对其他人有所借鉴。

利用word来解析带base64图片的富文本,思路是:word模板另存为mht格式,再修改后缀为ftl。将base64字符串的图片,填充到制作的模板上。
首先我们需要填充的word模板如下:
在这里插入图片描述
首先利用MicroSoft Word制作word模板,由于尝试新建word,后缀都是docx的,但是我们要求的模板是doc后缀的,因此在我们创建word模板后需要另存为doc格式(这点非常总要,坑了我好多天!!!!)。

在这里插入图片描述
我们的模板如下:
在这里插入图片描述

记住我们要替换的地方用 ${} 框起来,比如 $ {articleTitle} $ {equipmentTypeName} (去掉中间空格)。如上图所示。
将模板存为doc格式后,再利用MicroSoft Word另存为mht格式,可以利用idea进行打开。接下来就是对mht格式进行修改了。
需要修改的地方:

  1. 全文搜索gb2312,然后将其替换为utf-8,要记住同时需要加上3D前缀。
    需要改成一般如下格式(注意有的是单个双引号,有的是两个)
Content-Type: text/html; charset=3D"utf-8"
<meta http-equiv=3DContent-Type content=3D"text/html; charset=3Dutf-8">
  1. 同时在以下两个地方分别加入 $ {imagesBase64String} $ {imagesXmlHrefString},主要用于解析我们的base64图片。
    在这里插入图片描述

同时上图还有两个红色框框的地方,我们需要记住这些信息。后面有用。
修改后我们将mht后缀改成ftl。此外我们还要注意,格式转换后我们要替换的模板$ {articleTitle} 会填充一些其他字符,我们要将${}内不是我们输入的字符删掉,最终变成以下:
在这里插入图片描述
还有就是,后面我们转换以后会发现我们的中文字符会出现很多乱码,比如我们指定的字体等线格式会变成未知的问好(乱码),还有我们基本信息文章名称设备类型名称故障类型故障代码文章内容等中文字符都会变成乱码,我们可以手动输入中文进行修正。
在这里插入图片描述
带有显示的就是乱码。不过这些字体什么的不是这么重要,我们只需要把总要的我们模板的内容保证正确就行。
在这里插入图片描述

接下来是java代码部分了。
下面handler前三个set的内容就是我们之前mht用红色矩形框框起来的:“file:///C:/67194E70”“20201220.files”“01D6D6C1.A2FBEE20”

RichHtmlHandler handler = new RichHtmlHandler(String.valueOf(map.get("articleContent")));

handler.setDocSrcLocationPrex("file:///C:/67194E70");
handler.setDocSrcParent("20201220.files");
handler.setNextPartId("01D6D6C1.A2FBEE20");
handler.setShapeidPrex("_x56fe__x7247__x0020");
handler.setSpidPrex("_x0000_i");
handler.setTypeid("#_x0000_t75");

填充模板如下图所示,我们定义Map<String, Object>String就是我们之前模板里面 ${articleTitle}里面的字符,而Object则分别表示我们要填充的内容。

        Map<String, Object> map = new LinkedHashMap<>();

        for (KDTO kDTO : kDTOList) {//遍历集合往map中添加数据
            map.put("articleTitle", kDTO.getArticleTitle());
            map.put("articleNumbe", kDTO.getArticleCode());
            map.put("equipmentTypeName", kDTO.getEquipmentTypeName());
            map.put("faultType", String.valueOf(kDTO.getFailureType()));
            map.put("faultCode", kDTO.getArticleCode());
            map.put("faultPhenomenon", kDTO.getFailureSymptom());
            map.put("faultLocation", kDTO.getFailurePart());
            map.put("creator", kDTO.getStaffName());
            map.put("causeOfFailure", kDTO.getFailureReason());
            map.put("treatmentMeasures", kDTO.getFailureTreatment());
            map.put("articleContent", kDTO.getArticleContent());

我们通过freemarker进行模板填充,下面是html处理文章内容代码:

package com.fii.amms.utils;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;

public class RichHtmlHandler {

    private Document doc = null;
    private String html;

    private String docSrcParent = "";
    private String docSrcLocationPrex = "";
    private String nextPartId;
    private String shapeidPrex;
    private String spidPrex;
    private String typeid;

    private String handledDocBodyBlock;
    private List<String> docBase64BlockResults = new ArrayList<String>();
    private List<String> xmlImgRefs = new ArrayList<String>();

    public String getDocSrcLocationPrex() {
        return docSrcLocationPrex;
    }

    public void setDocSrcLocationPrex(String docSrcLocationPrex) {
        this.docSrcLocationPrex = docSrcLocationPrex;
    }

    public String getNextPartId() {
        return nextPartId;
    }

    public void setNextPartId(String nextPartId) {
        this.nextPartId = nextPartId;
    }

    public String getHandledDocBodyBlock() {
        String raw=   WordHtmlGeneratorHelper.string2Ascii(doc.getElementsByTag("body").html());
        return raw.replace("=3D", "=").replace("=", "=3D");
    }

    public String getRawHandledDocBodyBlock() {
        String raw=  doc.getElementsByTag("body").html();
        return raw.replace("=3D", "=").replace("=", "=3D");
    }
    public List<String> getDocBase64BlockResults() {
        return docBase64BlockResults;
    }

    public List<String> getXmlImgRefs() {
        return xmlImgRefs;
    }

    public String getShapeidPrex() {
        return shapeidPrex;
    }

    public void setShapeidPrex(String shapeidPrex) {
        this.shapeidPrex = shapeidPrex;
    }

    public String getSpidPrex() {
        return spidPrex;
    }

    public void setSpidPrex(String spidPrex) {
        this.spidPrex = spidPrex;
    }

    public String getTypeid() {
        return typeid;
    }

    public void setTypeid(String typeid) {
        this.typeid = typeid;
    }

    public String getDocSrcParent() {
        return docSrcParent;
    }

    public void setDocSrcParent(String docSrcParent) {
        this.docSrcParent = docSrcParent;
    }

    public String getHtml() {
        return html;
    }

    public void setHtml(String html) {
        this.html = html;
    }

    public RichHtmlHandler(String html) {
        this.html = html;
        doc = Jsoup.parse(wrappHtml(this.html));
    }

    public void re_init(String html){
        doc=null;
        doc = Jsoup.parse(wrappHtml(html));
        docBase64BlockResults.clear();
        xmlImgRefs.clear();
    }


    public void handledHtml(boolean isWebApplication)
            throws IOException {
        Elements imags = doc.getElementsByTag("img");
        if (imags == null || imags.size() == 0) {
            // 返回编码后字符串
            return;
            //handledDocBodyBlock = WordHtmlGeneratorHelper.string2Ascii(html);
        }

        // 转换成word mht 能识别图片标签内容,去替换html中的图片标签

        for (Element item : imags) {
            // 把文件取出来
            String src = item.attr("src");
            String srcRealPath = src;

            File imageFile = new File(srcRealPath);
            String imageFielShortName = imageFile.getName();
            //Base64的话获取fileTypeName;
            String fileTypeName = srcRealPath.substring(srcRealPath.indexOf("/")+1, srcRealPath.indexOf(";"));
            //String fileTypeName = WordImageConverter.getFileSuffix(srcRealPath);

            String docFileName = "image" + UUID.randomUUID().toString() + "."+ fileTypeName;
            String srcLocationShortName = docSrcParent + "/" + docFileName;

            String styleAttr = item.attr("style"); // 样式


            String encodedImg = null;
            BufferedImage sourceImg = null;
            String partSeparator = ",";
            if (srcRealPath.contains(partSeparator)) {
                encodedImg = srcRealPath.split(partSeparator)[1];
                byte[] decodedImg = java.util.Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8));
                sourceImg = ImageIO.read(new ByteArrayInputStream(decodedImg));
            }


            //高度
            String imagHeightStr=item.attr("height");;
            if(StringUtils.isEmpty(imagHeightStr)){
                imagHeightStr = getStyleAttrValue(styleAttr, "height");
            }
            //宽度
            String imagWidthStr=item.attr("width");;
            if(StringUtils.isEmpty(imagHeightStr)){
                imagHeightStr = getStyleAttrValue(styleAttr, "width");
            }

            imagHeightStr = imagHeightStr.replace("px", "");
            imagWidthStr = imagWidthStr.replace("px", "");
            if(StringUtils.isEmpty(imagHeightStr)){
                //去得到默认的文件高度
                imagHeightStr="0";
            }
            if(StringUtils.isEmpty(imagWidthStr)){
                imagWidthStr="0";
            }



            //int imageHeight = Integer.parseInt(imagHeightStr);
            //int imageWidth = Integer.parseInt(imagWidthStr);
            int imageHeight = sourceImg.getHeight();
            int imageWidth = sourceImg.getWidth();


            // 得到文件的word mht的body块
            String handledDocBodyBlock = WordImageConverter.toDocBodyBlock(srcRealPath,
                    imageFielShortName, imageHeight, imageWidth,styleAttr,
                    srcLocationShortName, shapeidPrex, spidPrex, typeid);

            //这里的顺序有点问题:应该是替换item,而不是整个后面追加
            //doc.rreplaceAll(item.toString(), handledDocBodyBlock);
            item.after(handledDocBodyBlock);
//			item.parent().append(handledDocBodyBlock);
            item.remove();
            // 去替换原生的html中的imag

            //String base64Content = WordImageConverter.imageToBase64(srcRealPath);
            String base64Content = encodedImg;
            String contextLoacation = docSrcLocationPrex + "/" + docSrcParent + "/" + docFileName;

            String docBase64BlockResult = WordImageConverter.generateImageBase64Block(nextPartId, contextLoacation,
                    fileTypeName, base64Content);
            docBase64BlockResults.add(docBase64BlockResult);

            String imagXMLHref = "<o:File HRef=3D\"" + docFileName + "\"/>";
            xmlImgRefs.add(imagXMLHref);

        }

    }

    private String getStyleAttrValue(String style, String attributeKey) {
        if (StringUtils.isEmpty(style)) {
            return "";
        }

        // 以";"分割
        String[] styleAttrValues = style.split(";");
        for (String item : styleAttrValues) {
            // 在以 ":"分割
            String[] keyValuePairs = item.split(":");
            if (attributeKey.equals(keyValuePairs[0])) {
                return keyValuePairs[1];
            }
        }

        return "";
    }

    private String wrappHtml(String html){
        // 因为传递过来都是不完整的doc
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<body>");
        sb.append(html);

        sb.append("</body>");
        sb.append("</html>");
        return sb.toString();
    }
}

处理完后我们可以将文章内容和base64图片分离:

           RichHtmlHandler handler = new RichHtmlHandler(String.valueOf(map.get("articleContent")));

            handler.setDocSrcLocationPrex("file:///C:/67194E70");
            handler.setDocSrcParent("20201220.files");
            handler.setNextPartId("01D6D6C1.A2FBEE20");
            handler.setShapeidPrex("_x56fe__x7247__x0020");
            handler.setSpidPrex("_x0000_i");
            handler.setTypeid("#_x0000_t75");

            try{
            handler.handledHtml(false);
            }catch (Exception e){
                throw new AMMSException(ErrorEnum.FAILED_TO_EXPORT.getCode(), "导出知识库失败", ErrorEnum.FAILED_TO_EXPORT.getMsg());
            }

            String bodyBlock = handler.getHandledDocBodyBlock();
            map.put("articleContent", bodyBlock);

            String handledBase64Block = "";
            if (handler.getDocBase64BlockResults() != null
                    && handler.getDocBase64BlockResults().size() > 0) {
                for (String item : handler.getDocBase64BlockResults()) {
                    handledBase64Block += item + "\n";
                }
            }
            map.put("imagesBase64String", handledBase64Block);

            String xmlimaHref = "";
            if (handler.getXmlImgRefs() != null
                    && handler.getXmlImgRefs().size() > 0) {
                for (String item : handler.getXmlImgRefs()) {
                    xmlimaHref += item + "\n";
                }
            }
            map.put("imagesXmlHrefString", xmlimaHref);

然后导出为ByteArrayOutputStream,这里主要是需要导出多篇文章,然后将文章压缩。

    public static ByteArrayOutputStream createWordBaos(Map<String, Object> dataMap, String templateName, ByteArrayOutputStream byteOut){

        WordHtmlGeneratorHelper.handleAllObject(dataMap);
        Writer w=null;
        try {
            Template t = configuration.getTemplate(templateName, "utf-8");
            w = new OutputStreamWriter(byteOut);
            t.process(dataMap, w);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        } finally {
        if(w != null){
            try {
                w.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
        return null;
    }

ByteArrayOutputStream数组储存多篇文章的ByteArrayOutputStream ,用String数组存储每个word文件的名字,将多个文件流合并到压缩包下载:

    public static void downLoadZip(ByteArrayOutputStream[] baoss, String[] fileNames, String zipName, HttpServletRequest request, HttpServletResponse response){
        //压缩包输出流
        ZipArchiveOutputStream zous = null;
        try {
        //压缩包响应输出流
            zous = new ZipArchiveOutputStream(response.getOutputStream());
            zous.setUseZip64(Zip64Mode.AsNeeded);
            //response.setContentType("application/octet-stream");
            //编码文件名并加上后缀(压缩包内部文件名不需要编码)
            String filename = encodeChineseDownloadFileName(request, zipName) + ".zip";
            response.setHeader("Content-disposition", "attachment; filename=" + filename);
            for(int i = 0; i < baoss.length; i++){
                ByteArrayOutputStream baos = baoss[i];
                //给文件名
                String fileName = fileNames[i];
                //下面三行是吧excel的文件以流的形式转为byte[]
                byte[] bytes = baos.toByteArray();
                ArchiveEntry entry = new ZipArchiveEntry(fileName);
                zous.putArchiveEntry(entry);
                zous.write(bytes);
                zous.closeArchiveEntry();
                if (baos != null) {
                    baos.close();
                }
            }
        } catch (IOException e) {
            throw new ASException(FAILED_TO_EXPORT.getCode(), "导出失败", FAILED_TO_EXPORT.getMsg());
        } catch (Exception e) {
            throw new ASException(FAILED_TO_EXPORT.getCode(), "导出失败", FAILED_TO_EXPORT.getMsg());
        } finally {
            try {
                if(zous != null){
                    zous.flush();
                    zous.close();
                }
            } catch (IOException e) {
                throw new ASException(FAILED_TO_EXPORT.getCode(), "导出失败", FAILED_TO_EXPORT.getMsg());
            }
        }
    }
            try {
                String fileName = URLEncoder.encode(new StringBuffer(knowledgeDTO.getArticleTitle())
                                .append(DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()))
                                .toString()
                        , ExcelData.UTF8) + ".doc";
                fileNameArray[temp] = fileName;

                Configuration cfg = new Configuration();
                cfg.setDirectoryForTemplateLoading(new File(System.getProperty("user.dir") + "/src/main/resources/static"));
                cfg.getTemplate("KnowledgeTemplate.ftl");
            }catch (Exception e){
                throw new AMMSException(ErrorEnum.FAILED_TO_EXPORT.getCode(), "导出知识库失败", ErrorEnum.FAILED_TO_EXPORT.getMsg());
            }

            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            WordGeneratorWithFreemarker.createWordBaos(map, "KnowledgeTemplate.ftl", byteOut);
            byteOutArray[temp] = byteOut;
            temp++;

        }

        String zipName = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
        WordUtil.downLoadZip(byteOutArray, fileNameArray, zipName, request, response);
    }

导出结果如下:
在这里插入图片描述

不过目前只能导出base64图片,其他的格式还待进一步研究。

参考文章
java通过freemarker导出包含富文本图片的word文档
Java导出html到word(带图片、富文本编辑器)
Java 实现HTML富文本导出至word完美解决方案
SpringBoot导出Word方式一:根据Word模板动态生成word(Poi-tl)
Springboot 项目导出word文档(文档内容包括数据以及服务器图片)
word-export
demo源码:https://pan.baidu.com/s/1bpj2mCn
poi-tl Spring下载word模板文件demo
springboot+easypoi按模板导出word,并打成zip包
POI实现html和word的相互转换(带图片)
最新实用版——JAVA使用POI替换Word模板中指定字符,并可插入图片。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Freemarker 是一种模板引擎,可用于动态生成文档,包括导出 Word 文档。在 Word 中拆分表格单元格是一个常见的需求,在使用 Freemarker 导出 Word 表格时,也可以通过一些技巧来实现。 首先,我们需要将表格数据传递给 Freemarker 模板。可以通过一个二维数组或者一个包多个 Map 的 List 来表示表格的数据。每个 Map 表示表格的一行数据,其中的 key 表示列的标题,value 表示该单元格的内容。 然后,在 Freemarker 模板中,可以使用表格标签来生成表格。使用嵌套的 #list 指令来遍历行数据,使用嵌套的 #items 指令来遍历列数据。可以通过设定参数来控制表格的样式,例如边框、背景颜色等。 接下来,需要判断是否需要拆分单元格。可以在模板中使用 #if 指令来判断某个条件是否满足,例如某个单元格的内容是否符合拆分的条件。如果需要拆分,则可以使用 colspan 和 rowspan 参数来控制单元格的合并和拆分。 需要注意的是,在拆分单元格时,应该确保表格的结构仍然正确,不会造成表格乱码或无法显示的问题。可以通过在表格标签中增加条件判断、控制行和列的合并等方式来调整表格结构。 最后,通过 Freemarker模板引擎将数据应用于模板,并将生成的 Word 文档导出保存即可。 总结起来,使用 Freemarker 导出 Word 表格并拆分单元格,需要将表格数据传递给 Freemarker 模板,通过判断条件来决定是否拆分单元格,并注意保持表格结构的正确性。这样就可以生成有拆分单元格的 Word 文档。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值