关于富文本html通过java生成并导出word

项目场景:

项目背景:由于前端用其他平台的缘故不支持直接引用三方库本地开发,仅可利用平台在线的方式。
现要求将合同生成word并在线下载。


问题描述

思路有很多,后端赋值并通过poi生成一篇word文档,通过前端处理富文本模板并将数据代入传至后台生成word文档

第一种思路:

1.制作word合同模板。
2.将值传递替换进模板占位符。
3.将替换完成模板导出。

这里试过这一种思路:弊端是代码量很多,在替换时会遍历每一行并将占位符进行替换,在此过程中有可能会将格式搞错,例如把下划线去掉了,改了字体颜色,多加部分空格下划线。

提供工具类
package app.modules.common.util;


import cn.hutool.core.lang.Assert;
import lombok.Data;
import lombok.Setter;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;


public class DynWordUtils {

    private final Logger logger = LoggerFactory.getLogger(DynWordUtils.class);

    /**
     * 被list替换的段落 被替换的都是oldParagraph
     */
    private XWPFParagraph oldParagraph;

    /**
     * 参数
     */
    private Map<String, Object> paramMap;

    /**
     * 当前元素的位置
     */
    int n = 0;

    /**
     * 判断当前是否是遍历的表格
     */
    boolean isTable = false;

    /**
     * 模板对象
     */
    XWPFDocument templateDoc;

    /**
     * 默认字体的大小
     */
    final int DEFAULT_FONT_SIZE = 10;

    /**
     * 重复模式的占位符所在的行索引
     */
    private int currentRowIndex;

    /**
     * 入口
     *
     * @param paramMap     模板中使用的参数
     * @param templatePaht 模板全路径
     * @param outPath      生成的文件存放的本地全路径
     */
    public static void process(Map<String, Object> paramMap, String templatePaht, String outPath, HttpServletRequest request, HttpServletResponse response) {
        DynWordUtils dynWordUtils = new DynWordUtils();
        dynWordUtils.setParamMap(paramMap);
        dynWordUtils.createWord(templatePaht, outPath,request,response);
    }

    private void setParamMap(Map<String, Object> paramMap) {
        this.paramMap = paramMap;
    }

    /**
     * 生成动态的word
     * @param templatePath
     * @param outPath
     */
    public void createWord(String templatePath, String outPath,HttpServletRequest request, HttpServletResponse response) {
        File inFile = new File(templatePath);
        try (OutputStream out = response.getOutputStream()) {


            templateDoc = new XWPFDocument(OPCPackage.open(inFile));
            parseTemplateWord();
            //写入流中
//                    inputStream =new FileInputStream(inFile);
            //获取响应流
            // 写入响应流中
//            byte[] buffer = new byte[inputStream.available()];
//            // 流中写入的是字节
//            int bytesRead;
//            // 读入流写出流
//            while ((bytesRead = inputStream.read(buffer)) != -1) {
//                outputStream.write(buffer, 0, bytesRead);
//            }
            //设置响应头和contentType
            response.setHeader("Content-Disposition", "attachment;filename=exported_document.docx");
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            templateDoc.write(out);
            out.flush();
            // 结束关流,不关流会导致文件损坏
            out.close();

        } catch (Exception e) {
            StackTraceElement[] stackTrace = e.getStackTrace();

            String className = stackTrace[0].getClassName();
            String methodName = stackTrace[0].getMethodName();
            int lineNumber = stackTrace[0].getLineNumber();

            logger.error("错误:第:{}行, 类名:{}, 方法名:{}", lineNumber, className, methodName);
            throw new RuntimeException(e.getCause().getMessage());
        }
    }

    /**
     * 解析word模板
     */
    public void parseTemplateWord() throws Exception {

        List<IBodyElement> elements = templateDoc.getBodyElements();

        for (; n < elements.size(); n++) {
            IBodyElement element = elements.get(n);
            // 普通段落
            if (element instanceof XWPFParagraph) {

                XWPFParagraph paragraph = (XWPFParagraph) element;
                oldParagraph = paragraph;
                if (paragraph.getParagraphText().isEmpty()) {
                    continue;
                }

                delParagraph(paragraph);

            }
            else if (element instanceof XWPFTable) {
                // 表格
                isTable = true;
                XWPFTable table = (XWPFTable) element;

                delTable(table, paramMap);
                isTable = false;
            }
        }

    }

    /**
     * 处理段落
     */
    private void delParagraph(XWPFParagraph paragraph) throws Exception {
        List<XWPFRun> runs = oldParagraph.getRuns();
        StringBuilder sb = new StringBuilder();
        for (XWPFRun run : runs) {
            String text = run.getText(0);
            if (text == null) {
                continue;
            }
            sb.append(text);
            run.setText("", 0);
        }
        Placeholder(paragraph, runs, sb);
    }


    /**
     * 匹配传入信息集合与模板
     *
     * @param placeholder 模板需要替换的区域()
     * @param paramMap    传入信息集合
     * @return 模板需要替换区域信息集合对应值
     */
    public void changeValue(XWPFRun currRun, String placeholder, Map<String, Object> paramMap) throws Exception {

        String placeholderValue = placeholder;
        if (paramMap == null || paramMap.isEmpty()) {
            return;
        }
//        if (str!=null&&str.contains(PoiWordUtils.underlineRowText)) {
//            currRun.setUnderline(UnderlinePatterns.SINGLE);
//            placeholderValue = placeholder.replace(PoiWordUtils.underlineRowText,"");
//        }
        Map<String,Boolean> stringBooleanSet= new HashMap<>();
        Set<Map.Entry<String, Object>> textSets = paramMap.entrySet();
        for (Map.Entry<String, Object> textSet : textSets) {
            //匹配模板与替换值 格式${key}
            String mapKey = textSet.getKey();
            String docKey = PoiWordUtils.getDocKey(mapKey);
            if (placeholderValue.indexOf(docKey) != -1) {
                Object obj = textSet.getValue();
                // 需要添加一个list
                if (obj instanceof List) {
                    placeholderValue = delDynList(placeholder, (List) obj);
                } else {
                    placeholderValue = placeholderValue.replaceAll(PoiWordUtils.getPlaceholderReg(mapKey), String.valueOf(obj));
                }

            }
        }
        currRun.setText(placeholderValue, 0);
    }

    /**
     * 处理的动态的段落(参数为list)
     *
     * @param placeholder 段落占位符
     * @param obj
     * @return
     */
    private String delDynList(String placeholder, List obj) {
        String placeholderValue = placeholder;
        List dataList = obj;
        Collections.reverse(dataList);
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Object text = dataList.get(i);
            // 占位符的那行, 不用重新创建新的行
            if (i == 0) {
                placeholderValue = String.valueOf(text);
            } else {
                XWPFParagraph paragraph = createParagraph(String.valueOf(text));
                if (paragraph != null) {
                    oldParagraph = paragraph;
                }
                // 增加段落后doc文档会的element的size会随着增加(在当前行的上面添加),回退并解析新增的行(因为可能新增的带有占位符,这里为了支持图片和表格)
                if (!isTable) {
                    n--;
                }
            }
        }
        return placeholderValue;
    }

    /**
     * 创建段落 <p></p>
     *
     * @param texts
     */
    public XWPFParagraph createParagraph(String... texts) {

        // 使用游标创建一个新行
        XmlCursor cursor = oldParagraph.getCTP().newCursor();
        XWPFParagraph newPar = templateDoc.insertNewParagraph(cursor);
        // 设置段落样式
        newPar.getCTP().setPPr(oldParagraph.getCTP().getPPr());
        copyParagraph(oldParagraph, newPar, texts);
        return newPar;
    }

    /**
     * 处理表格(遍历)
     *
     * @param table    表格
     * @param paramMap 需要替换的信息集合
     */
    public void delTable(XWPFTable table, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 0, size = rows.size(); i < size; i++) {
            XWPFTableRow row = rows.get(i);
            currentRowIndex = i;
            // 如果是动态添加行 直接处理后返回
            if (delAndJudgeRow(table, paramMap, row)) {
                return;
            }
        }
    }

    /**
     * 判断并且是否是动态行,并且处理表格占位符
     * @param table 表格对象
     * @param paramMap 参数map
     * @param row 当前行
     * @return
     * @throws Exception
     */
    private boolean delAndJudgeRow(XWPFTable table, Map<String, Object> paramMap, XWPFTableRow row) throws Exception {
        // 当前行是动态行标志
        if (PoiWordUtils.isAddRow(row)) {
            List<XWPFTableRow> xwpfTableRows = addAndGetRows(table, row, paramMap);
            // 回溯添加的行,这里是试图处理动态添加的图片
            for (XWPFTableRow tbRow : xwpfTableRows) {
                delAndJudgeRow(table, paramMap, tbRow);
            }
            return true;
        }

        // 如果是重复添加的行
        if (PoiWordUtils.isAddRowRepeat(row)) {
            List<XWPFTableRow> xwpfTableRows = addAndGetRepeatRows(table, row, paramMap);
            // 回溯添加的行,这里是试图处理动态添加的图片
            for (XWPFTableRow tbRow : xwpfTableRows) {
                delAndJudgeRow(table, paramMap, tbRow);
            }
            return true;
        }
        // 当前行非动态行标签
        List<XWPFTableCell> cells = row.getTableCells();
        for (XWPFTableCell cell : cells) {
            //判断单元格是否需要替换
            if (PoiWordUtils.checkText(cell.getText())) {
                List<XWPFParagraph> paragraphs = cell.getParagraphs();
                for (XWPFParagraph paragraph : paragraphs) {
                    List<XWPFRun> runs = paragraph.getRuns();
                    StringBuilder sb = new StringBuilder();
                    for (XWPFRun run : runs) {
                        sb.append(run.toString());
                        run.setText("", 0);
                    }
                    Placeholder(paragraph, runs, sb);
                }
            }
        }
        return false;
    }

    /**
     * 处理占位符
     * @param runs 当前段的runs
     * @param sb 当前段的内容
     * @throws Exception
     */
    private void Placeholder(XWPFParagraph currentPar, List<XWPFRun> runs, StringBuilder sb) throws Exception {
        if (runs.size() > 0) {
            String text = sb.toString();
            List<XWPFRun> newRuns = currentPar.getRuns();
            XWPFRun currRun = runs.get(0);
            if (PoiWordUtils.isPicture(text)) {
                // 该段落是图片占位符
            } else {
                changeValue(currRun, text, paramMap);
            }
        }
    }



    /**
     * 添加行  标签行不是新创建的
     *  flagRow  flagRow 表有标签的行
     */
    private List<XWPFTableRow> addAndGetRows(XWPFTable table, XWPFTableRow flagRow, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableCell> flagRowCells = flagRow.getTableCells();
        XWPFTableCell flagCell = flagRowCells.get(0);

        String text = flagCell.getText();
        List<List<String>> dataList = (List<List<String>>) PoiWordUtils.getValueByPlaceholder(paramMap, text);

        // 新添加的行
        List<XWPFTableRow> newRows = new ArrayList<>(dataList.size());
        if (dataList == null || dataList.size() <= 0) {
            return newRows;
        }

        XWPFTableRow currentRow = flagRow;
        int cellSize = flagRow.getTableCells().size();
        for (int i = 0, size = dataList.size(); i < size; i++) {
            if (i != 0) {
                currentRow = table.createRow();
                // 复制样式
                if (flagRow.getCtRow() != null) {
                    currentRow.getCtRow().setTrPr(flagRow.getCtRow().getTrPr());
                }
            }
            addRow(flagCell, currentRow, cellSize, dataList.get(i));
            newRows.add(currentRow);
        }
        return newRows;
    }

    /**
     * 添加重复多行 动态行  每一行都是新创建的
     */
    private List<XWPFTableRow> addAndGetRepeatRows(XWPFTable table, XWPFTableRow flagRow, Map<String, Object> paramMap) throws Exception {
        List<XWPFTableCell> flagRowCells = flagRow.getTableCells();
        XWPFTableCell flagCell = flagRowCells.get(0);
        String text = flagCell.getText();
        List<List<String>> dataList = (List<List<String>>) PoiWordUtils.getValueByPlaceholder(paramMap, text);
        String tbRepeatMatrix = PoiWordUtils.getTbRepeatMatrix(text);


        // 新添加的行
        List<XWPFTableRow> newRows = new ArrayList<>();
        if (dataList == null || dataList.size() <= 0) {
            return newRows;
        } else {
             newRows = new ArrayList<>(dataList.size());
        }

        String[] split = tbRepeatMatrix.split(",");
        int startRow = Integer.parseInt(split[0]);
        int endRow = Integer.parseInt(split[1]);
        int startCell = Integer.parseInt(split[2]);
        int endCell = Integer.parseInt(split[3]);

        XWPFTableRow currentRow;
        for (int i = 0, size = dataList.size(); i < size; i++) {
            int flagRowIndex = i % (endRow - startRow + 1);
            XWPFTableRow repeatFlagRow = table.getRow(flagRowIndex);
            // 清除占位符那行
            if (i == 0) {
                table.removeRow(currentRowIndex);
            }
            currentRow = table.createRow();
            // 复制样式
            if (repeatFlagRow.getCtRow() != null) {
                currentRow.getCtRow().setTrPr(repeatFlagRow.getCtRow().getTrPr());
            }
            addRowRepeat(startCell, endCell, currentRow, repeatFlagRow, dataList.get(i));
            newRows.add(currentRow);
        }
        return newRows;
    }

    /**
     * 根据模板cell添加新行
     *
     * @param flagCell    模板列(标记占位符的那个cell)
     * @param row         新增的行
     * @param cellSize    每行的列数量(用来补列补足的情况)
     * @param rowDataList 每行的数据
     */
    private void addRow(XWPFTableCell flagCell, XWPFTableRow row, int cellSize, List<String> rowDataList) {
        for (int i = 0; i < cellSize; i++) {
            XWPFTableCell cell = row.getCell(i);
            cell = cell == null ? row.createCell() : row.getCell(i);
            if (i < rowDataList.size()) {
                PoiWordUtils.copyCellAndSetValue(flagCell, cell, rowDataList.get(i));
            } else {
                // 数据不满整行时,添加空列
                PoiWordUtils.copyCellAndSetValue(flagCell, cell, "");
            }
        }
    }

    /**
     * 根据模板cell  添加重复行
     * @param startCell 模板列的开始位置
     * @param endCell 模板列的结束位置
     * @param currentRow 创建的新行
     * @param repeatFlagRow 模板列所在的行
     * @param rowDataList 每行的数据
     */
    private void addRowRepeat(int startCell, int endCell, XWPFTableRow currentRow, XWPFTableRow repeatFlagRow, List<String> rowDataList) {
        int cellSize = repeatFlagRow.getTableCells().size();
        for (int i = 0; i < cellSize; i++) {
            XWPFTableCell cell = currentRow.getCell(i);
            cell = cell == null ? currentRow.createCell() : currentRow.getCell(i);
            int flagCellIndex = i % (endCell - startCell + 1);
            XWPFTableCell repeatFlagCell = repeatFlagRow.getCell(flagCellIndex);
            if (i < rowDataList.size()) {
                PoiWordUtils.copyCellAndSetValue(repeatFlagCell, cell, rowDataList.get(i));
            } else {
                // 数据不满整行时,添加空列
                PoiWordUtils.copyCellAndSetValue(repeatFlagCell, cell, "");
            }
        }
    }

    /**
     * 复制段落
     *
     * @param sourcePar 原段落
     * @param targetPar
     * @param texts
     */
    private void copyParagraph(XWPFParagraph sourcePar, XWPFParagraph targetPar, String... texts) {

        targetPar.setAlignment(sourcePar.getAlignment());
        targetPar.setVerticalAlignment(sourcePar.getVerticalAlignment());

        // 设置布局
        targetPar.setAlignment(sourcePar.getAlignment());
        targetPar.setVerticalAlignment(sourcePar.getVerticalAlignment());

        if (texts != null && texts.length > 0) {
            String[] arr = texts;
            XWPFRun xwpfRun = sourcePar.getRuns().size() > 0 ? sourcePar.getRuns().get(0) : null;

            for (int i = 0, len = texts.length; i < len; i++) {
                String text = arr[i];
                XWPFRun run = targetPar.createRun();

                run.setText(text);

                run.setFontFamily(xwpfRun.getFontFamily());
                int fontSize = xwpfRun.getFontSize();
                run.setFontSize((fontSize == -1) ? DEFAULT_FONT_SIZE : fontSize);
                run.setBold(xwpfRun.isBold());
                run.setItalic(xwpfRun.isItalic());
            }
        }
    }

}

package app.modules.common.util;

import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.Optional;


public class PoiWordUtils {

    /**
     * 占位符第一个字符
     */
    public static final String PREFIX_FIRST = "$";

    /**
     * 占位符第二个字符
     */
    public static final String PREFIX_SECOND = "{";

    /**
     * 占位符的前缀
     */
    public static final String PLACEHOLDER_PREFIX = PREFIX_FIRST + PREFIX_SECOND;

    /**
     * 占位符后缀
     */
    public static final String PLACEHOLDER_END = "}";
    /**
     * 下划线格式标记
     * */
    public static final String underlineRowText = "underline:";
    /**
     * 表格中需要动态添加行的独特标记
     */
    public static final String addRowText = "tbAddRow:";

    public static final String addRowRepeatText = "tbAddRowRepeat:";

    /**
     * 表格中占位符的开头 ${tbAddRow:  例如${tbAddRow:tb1}
     */
    public static final String addRowFlag = PLACEHOLDER_PREFIX + addRowText;

    /**
     * 表格中占位符的开头 ${tbAddRowRepeat:  例如 ${tbAddRowRepeat:0,2,0,1} 第0行到第2行,第0列到第1列 为模板样式
     */
    public static final String addRowRepeatFlag = PLACEHOLDER_PREFIX + addRowRepeatText;

    /**
     * 重复矩阵的分隔符  比如:${tbAddRowRepeat:0,2,0,1} 分隔符为 ,
     */
    public static final String tbRepeatMatrixSeparator = ",";

    /**
     * 占位符的后缀
     */
    public static final String PLACEHOLDER_SUFFIX = "}";

    /**
     * 图片占位符的前缀
     */
    public static final String PICTURE_PREFIX = PLACEHOLDER_PREFIX + "image:";
    private static final Logger log = LoggerFactory.getLogger(PoiWordUtils.class);

    /**
     * 判断当前行是不是标志表格中需要添加行
     *
     * @param row
     * @return
     */
    public static boolean isAddRow(XWPFTableRow row) {
        return isDynRow(row, addRowFlag);
    }

    /**
     * 添加重复模板动态行(以多行为模板)
     * @param row
     * @return
     */
    public static boolean isAddRowRepeat(XWPFTableRow row) {
        return isDynRow(row, addRowRepeatFlag);
    }

    private static boolean isDynRow(XWPFTableRow row, String dynFlag) {
        if (row == null) {
            return false;
        }
        List<XWPFTableCell> tableCells = row.getTableCells();
        if (tableCells != null) {
            XWPFTableCell cell = tableCells.get(0);
            if (cell != null) {
                String text = cell.getText();
                if (text != null && text.startsWith(dynFlag)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 从参数map中获取占位符对应的值
     *
     * @param paramMap
     * @param key
     * @return
     */
    public static Object getValueByPlaceholder(Map<String, Object> paramMap, String key) {
        if (paramMap != null) {
            if (key != null) {
                return paramMap.get(getKeyFromPlaceholder(key));
            }
        }
        return null;
    }

    /**
     * 后去占位符的重复行列矩阵
     * @param key 占位符
     * @return {0,2,0,1}
     */
    public static String getTbRepeatMatrix(String key) {
        String $1 = key.replaceAll("\\" + PREFIX_FIRST + "\\" + PREFIX_SECOND + addRowRepeatText + "(.*)" + "\\" + PLACEHOLDER_SUFFIX, "$1");
        return $1;
    }

    /**
     * 从占位符中获取key
     *
     * @return
     */
    public static String getKeyFromPlaceholder(String placeholder) {
        return Optional.ofNullable(placeholder).map(p -> p.replaceAll("[\\$\\{\\}]", "")).get();
    }

    /**
     * 复制列的样式,并且设置值
     * @param sourceCell
     * @param targetCell
     * @param text
     */
    public static void copyCellAndSetValue(XWPFTableCell sourceCell, XWPFTableCell targetCell, String text) {
        //段落属性
        List<XWPFParagraph> sourceCellParagraphs = sourceCell.getParagraphs();
        if (sourceCellParagraphs == null || sourceCellParagraphs.size() <= 0) {
            return;
        }
        XWPFParagraph sourcePar = sourceCellParagraphs.get(0);
        XWPFParagraph targetPar = targetCell.getParagraphs().get(0);

        // 设置段落的样式
        targetPar.getCTP().setPPr(sourcePar.getCTP().getPPr());
//        CTTcBorders tcBorders = sourceCell.getCTTc().getTcPr().getTcBorders();
//        targetCell.getCTTc().getTcPr().setTcBorders(tcBorders);

        List<XWPFRun> sourceParRuns = sourcePar.getRuns();
        if (sourceParRuns != null && sourceParRuns.size() > 0) {
            // 如果当前cell中有run
            List<XWPFRun> runs = targetPar.getRuns();
            Optional.ofNullable(runs).ifPresent(rs -> rs.stream().forEach(r -> r.setText("", 0)));
            if (runs != null && runs.size() > 0) {
                runs.get(0).setText(text, 0);
            } else {
                XWPFRun cellR = targetPar.createRun();
                cellR.setText(text, 0);
                // 设置列的样式位模板的样式
                targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
            }
            setTypeface(sourcePar, targetPar);
        } else {
            targetCell.setText(text);
        }
    }
    /**
     * 复制字体
     */
    private static void setTypeface(XWPFParagraph sourcePar, XWPFParagraph targetPar) {
        XWPFRun sourceRun = sourcePar.getRuns().get(0);
        String fontFamily = sourceRun.getFontFamily();
        //int fontSize = sourceRun.getFontSize();
        String color = sourceRun.getColor();
//        String fontName = sourceRun.getFontName();
        boolean bold = sourceRun.isBold();
        boolean italic = sourceRun.isItalic();
        int kerning = sourceRun.getKerning();
//        String style = sourcePar.getStyle();
        UnderlinePatterns underline = sourceRun.getUnderline();

        XWPFRun targetRun = targetPar.getRuns().get(0);
        targetRun.setFontFamily(fontFamily);
//        targetRun.setFontSize(fontSize == -1 ? 10 : fontSize);
        targetRun.setBold(bold);
        targetRun.setColor(color);
        log.info("存在颜色变动=====》");
        targetRun.setItalic(italic);
        targetRun.setKerning(kerning);
        targetRun.setUnderline(underline);
        //targetRun.setFontSize(fontSize);
    }
    /**
     * 判断文本中时候包含$
     * @param text 文本
     * @return 包含返回true,不包含返回false
     */
    public static boolean checkText(String text){
        boolean check  =  false;
        if(text.indexOf(PLACEHOLDER_PREFIX)!= -1){
            check = true;
        }
        return check;
    }

    /**
     * 获得占位符替换的正则表达式
     * @return
     */
    public static String getPlaceholderReg(String text) {
        return "\\" + PREFIX_FIRST + "\\" + PREFIX_SECOND + text + "\\" + PLACEHOLDER_SUFFIX;
    }

    public static String getDocKey(String mapKey) {
        return PLACEHOLDER_PREFIX + mapKey + PLACEHOLDER_SUFFIX;
    }

    /**
     * 判断当前占位符是不是一个图片占位符
     * @param text
     * @return
     */
    public static boolean isPicture(String text) {
        return text.startsWith(PICTURE_PREFIX);
    }

    /**
     * 删除一行的列
     * @param row
     */
    public static void removeCells(XWPFTableRow row) {
        int size = row.getTableCells().size();
        try {
            for (int i = 0; i < size; i++) {
                row.removeCell(i);
            }
        } catch (Exception e) {

        }
    }
}

  1. 调用部分
 // 处理导出合同
    @PostMapping("/getContractWord")
    public void getContractWord(@RequestBody Map<String, Object> jsonObject, HttpServletRequest request, HttpServletResponse response) {
        Map map =new HashMap<>();
        map.put("customerName","高密海绵厂");
        map.put("salesName","烟台顺达聚氨酯有限责任公司");
        map.put("contractNo","123456");
        log.info("============>{}",map.toString());
        DynWordUtils.process(map,"D:\\bbb.docx","D:\\aaa.doc",request,response);

    }

第二种思路

1.前端准备富文本html利用原生css处理格式。
2.前端利用es6模板语法将html中的关键字段值替换掉
3.传递后端生成word 并写入流中

1.html很多 这里只取片段 包含普通格式以及表格处理

es6只需要代码块中存在声明占位符部分,模板会自动填充

  let data =  this.value
  const date = new Date(data.createdTime); // 获取当前日期
  const year = date.getFullYear(); // 获取年份
  const month = date.getMonth() + 1; // 获取月份,需要加1,因为月份是从0开始计数的
  const day = date.getDate(); // 获取日期
  const formattedDate = `${year}${month}${day}`;
  let html1 =
      `<div>
    <div class="title contentheight">
      <div id="pdfDom2" style="margin: auto">
        <p align="center">
          <span style="font-size: 22pt; font-weight: 900">工业品买卖合同</span>
        </p>
        <div align="center">
          <table
              border="0"
              cellspacing="0"
              cellpadding="0"
              width="100%"
              class="ke-zeroborder"
          >
            <tbody>
            <tr>
              <td width="61%">
                <p>供方:<span style="text-decoration: underline;  ">${data.orgName}</span></p>
              </td>
              <td width="39%">
                <p><span>合同编号:</span><span style="text-decoration: underline;  ">${data.formNo}</span></p>
              </td>
            </tr>
            <tr>
              <td width="50%"></td>
              <td width="50%">
                <p><span>签订方式:</span><span>传真、扫描件</span></p>
              </td>
            </tr>
            <tr>
              <td width="50%">
                <p>需方:<span class="lspacing1" style="text-decoration: underline;  ">${data.CustomerName}</span></p>
              </td>
              <td width="50%">
                <p><span class="lspacing2">签订时间:${formattedDate}</span></p>
              </td>
            </tr>
            <tr>
              <td colspan="6">
                <p><span>第一条 产品规格、数量、价款</span></p>
              </td>
            </tr>
            <tr>
              <td colspan="8">
                <table
                    border="1"
                    cellspacing="0"
                    cellpadding="5"
                    width="100%"
                    class="collapse"
                >
                  <tbody>
                  <tr>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        序号
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        产品名称
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        牌号
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        规格
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        数量(吨)
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        单价(元/吨)
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        总金额(元)
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        备注
                      </p>
                    </td>
                  </tr>`
  let data1 = this.value.ws_contractentry
  data1.forEach((item,index)=>{
    html1+=`    <tr>
                    <td>
                      <p class="lineheight14" style="text-align: center">${index+1}</p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.groupName}</span>
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.mNameIdText}</span>
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.model}</span>
                      </p>
                    </td>
                    <td>
                      <p style="text-align: center"><span>${item.num}</span></p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.rPrice}</span>
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.sum}</span>
                      </p>
                    </td>
                    <td>
                      <p class="lineheight14" style="text-align: center">
                        <span>${item.remark}</span>
                      </p>
                    </td>
                  </tr>`;

  })
  // 发送数据到服务器
  fetch(`${config.apiHost}/common/createWord`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ html: html1+html2 })
  })
      .then(response => response.blob())
      .then(blob => {
        // 创建一个下载链接并触发下载
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'document.docx';  // 文件名
        document.body.appendChild(a);
        a.click();
        a.remove();
      })
      .catch(error => console.error('Error:', error));
     
java部分 html转成word导出
 @PostMapping("/createWord")
    public void generateWord(@RequestBody Map<String,String> html, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 读取富文本数据
        String htmlContent = html.get("html");
        // 必须要html标识的标签 否则可能会解析不出来 
         htmlContent = "<html><head></head><body>"+htmlContent+"</body></html>";
        ByteArrayInputStream byteArrayInputStream = null;
        POIFSFileSystem poifsFileSystem = null;
        ServletOutputStream servletOutputStream = null;
        try {
            byte b[] = htmlContent.getBytes("GBK"); //这里是必须要设置编码的,不然导出中文就会乱码。
            byteArrayInputStream = new ByteArrayInputStream(b);//将字节数组包装到流中
            poifsFileSystem = new POIFSFileSystem();
            DirectoryEntry directory = poifsFileSystem.getRoot();
            directory.createDocument("WordDocument", byteArrayInputStream);
            //输出文件
            request.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");//导出word格式
            response.addHeader("Content-Disposition", "attachment;filename=aaaa.doc");
            servletOutputStream = response.getOutputStream();
            poifsFileSystem.writeFilesystem(servletOutputStream);
            byteArrayInputStream.close();
            servletOutputStream.close();
            poifsFileSystem.close();
        } catch (Exception e) {
            throw e;
        }
    }

第二种思路的弊端是poifsFileSystem不支持二次修改,例如修改页边距 修改字体颜色 调整文件内容,我尝试了一下poifsFileSystem转HWPFDdocument对象没找到合适的解决方法 所以暂定不能二次修改。


最后效果

在这里插入图片描述

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用Apache POI库来实现Java富文本导出Word。具体步骤如下: 1. 添加Apache POI库依赖,可以在Maven或Gradle中添加如下依赖: ```xml <!-- Apache POI --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> ``` 2. 创建一个新的Word文档: ```java XWPFDocument document = new XWPFDocument(); ``` 3. 添加段落和文本: ```java XWPFParagraph paragraph = document.createParagraph(); XWPFRun run = paragraph.createRun(); run.setText("Hello, World!"); ``` 4. 添加富文本样式,例如字体、颜色、加粗等: ```java run.setBold(true); run.setColor("FF0000"); run.setFontSize(20); ``` 5. 导出Word文档: ```java FileOutputStream out = new FileOutputStream("output.docx"); document.write(out); out.close(); document.close(); ``` 完整的示例代码如下: ```java import java.io.FileOutputStream; import java.io.IOException; import org.apache.poi.xwpf.usermodel.*; public class RichTextToWord { public static void main(String[] args) throws IOException { XWPFDocument document = new XWPFDocument(); XWPFParagraph paragraph = document.createParagraph(); XWPFRun run = paragraph.createRun(); run.setText("Hello, World!"); run.setBold(true); run.setColor("FF0000"); run.setFontSize(20); FileOutputStream out = new FileOutputStream("output.docx"); document.write(out); out.close(); document.close(); } } ``` 运行该程序将会在当前目录下生成一个名为`output.docx`的Word文档,其中包含了一个加粗、红色、20号字体的Hello, World!文本。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值