HeavenTang

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

java导出占位符word模板

实际项目中,便于维护模板,采用直接 word里面制作占位符 来导出更为直观,而不是 将word做成tpl模板。

使用XWPFDocument (这种解析xlsx或者docx)和 HWPFDocument(这种解析xls或者doc)。 代码如下:

写磁盘代码: 点击查看代码
// 创建Word模板文件:在开始代码编写之前,我们需要准备一个Word模板文件,模板文件中的文本可以是固定的,也可以使用占位符来表示需要动态填充的内容
        //读取Word模板文件
        // 我们需要使用Apache POI库来读取Word模板文件,并将其加载到内存中进行后续的操作。代码如下
        FileInputStream fis = null;
        try {
            File sPath = new File("D:/opt/jwxt/file/uploadfile/ly-edu-core-svc-thx/testWord.docx");
//            File sPath = new File("/template/testword.doc");
            fis = new FileInputStream(sPath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
//        HWPFDocument 支持doc  XWPFDocument支持docx
 
        XWPFDocument document = null;
        FileOutputStream fos = null;
        try {
            document = new XWPFDocument(fis);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }

        // 替换模板中的占位符
        //在模板文件中,我们可以使用占位符来表示需要动态填充的内容,比如使用${placeholder}来表示占位符。我们需要遍历Word文档的所有段落和表格,找到包含占位符的位置,并将其替换为实际的内容。代码如下

        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                String text = run.getText(0);
                if (text != null && text.contains("${{成果名称}}")) {
                    text = text.replace("${{成果名称}}", "替换后的内容");
                    run.setText(text, 0);
                }
            }
        }

        // 表格处理方式
        List<XWPFTable> tables = document.getTables();
        for (XWPFTable table : tables) {
            List<XWPFTableRow> rows = table.getRows();
            for (XWPFTableRow row : rows) {
                List<XWPFTableCell> cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    List<XWPFParagraph> cellParagraphs = cell.getParagraphs();
                    for (XWPFParagraph cellParagraph : cellParagraphs) {
                        List<XWPFRun> runs = cellParagraph.getRuns();
                        for (XWPFRun run : runs) {
                            String text = run.getText(0);
                            if (text != null && text.contains("${{完成人姓名1}}")) {
                                text = text.replace("${{完成人姓名1}}", "替换后的内容");
                                run.setText(text, 0);
                            }
                            if (text != null && text.contains("${{成果名称}}")) {
                                text = text.replace("${{成果名称}}", "替换后的内容2222");
                                run.setText(text, 0);
                            }
                        }
                    }
                }
            }
        }

        // 导出word文档
            File outFile = new File("D:\\opt\\jwxt\\file\\uploadfile\\ly-edu-core-svc-thx\\占位符完成打印.docx");
        fos = new FileOutputStream(outFile);

    } catch (Exception e) {
        log.error( "获取word文档失败,原因是:"   + e.getMessage());
    } finally {
        try {
            document.write(fos);
            if(fos != null) {
                fos.close();
            }
//            document.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

与前端配合弹出流,使用response,代码如下

点击查看代码
/**
     * 我们可以实现Java Word模板打印的功能。首先,我们需要引入Apache POI库,并创建一个Word模板文件。然后,我们需要读取模板文件并将其加载到内存中。
     * 接下来,我们需要遍历Word文档,找到包含占位符的位置,并将其替换为实际的内容。最后,我们可以将生成的Word文档导出为文件。
     * <p>
     * 在实际应用中,我们可以根据具体需求扩展这个功能,比如支持更复杂的模板替换、添加图片和表格等。同时,我们也可以使用其他的库来实现类似的功能,比如Docx4j等
     *
     * 
     */

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private HttpServletRequest request;

    @Override
    public void testWordPrint(ReviewTemplateDto reviewTemplateDto) { 


        // 创建Word模板文件:在开始代码编写之前,我们需要准备一个Word模板文件,模板文件中的文本可以是固定的,也可以使用占位符来表示需要动态填充的内容
        //读取Word模板文件
        // 我们需要使用Apache POI库来读取Word模板文件,并将其加载到内存中进行后续的操作。代码如下
        FileInputStream fis = null;
        try {
            File sPath = new File("D:/opt/jwxt/file/uploadfile/tt/模板.docx");
//            File sPath = new File("/template/testword.doc");
            fis = new FileInputStream(sPath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
//        HWPFDocument 支持doc  XWPFDocument支持docx
        XWPFDocument document = null;
        FileOutputStream fos = null;
        try {
            document = new XWPFDocument(fis); 

            // 替换模板中的占位符
            //在模板文件中,我们可以使用占位符来表示需要动态填充的内容,比如使用${placeholder}来表示占位符。我们需要遍历Word文档的所有段落和表格,找到包含占位符的位置,并将其替换为实际的内容。代码如下

            List<XWPFParagraph> paragraphs = document.getParagraphs();
            for (XWPFParagraph paragraph : paragraphs) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    String text = run.getText(0);
                    if (text != null && text.contains("${{成果名称}}")) {
                        text = text.replace("${{成果名称}}", "替换后的内容");
                        run.setText(text, 0);
                    }
                }
            }

            // 表格处理方式
            List<XWPFTable> tables = document.getTables();
            for (XWPFTable table : tables) {
                List<XWPFTableRow> rows = table.getRows();
                for (XWPFTableRow row : rows) {
                    List<XWPFTableCell> cells = row.getTableCells();
                    for (XWPFTableCell cell : cells) {
                        List<XWPFParagraph> cellParagraphs = cell.getParagraphs();
                        for (XWPFParagraph cellParagraph : cellParagraphs) {
                            List<XWPFRun> runs = cellParagraph.getRuns();
                            for (XWPFRun run : runs) {
                                String text = run.getText(0);
                                if (text != null && text.contains("${{完成人姓名1}}")) {
                                    text = text.replace("${{完成人姓名1}}", "替换后的内容");
                                    run.setText(text, 0);
                                }
                                if (text != null && text.contains("${{成果名称}}")) {
                                    text = text.replace("${{成果名称}}", "替换后的内容2222");
                                    run.setText(text, 0);
                                }
                            }
                        }
                    }
                }
            }

            // 导出word文档
//            File outFile = new File("D:\\opt\\jwxt\\file\\uploadfile\\tt\\tt.docx");// 直接写磁盘
//            fos = new FileOutputStream(outFile);
//
////            document.write(fos);//
            String filePath = request.getSession().getServletContext().getRealPath("/");
            String fileName = "tt.docx";
//            response.setHeader("Content-Disposition", "attachment;fileName=" + stringToUnicode(fileName));
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("multipart/form-data");

            OutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            document.write(outputStream);

        } catch (Exception e) {
            log.error("获取word文档失败,原因是:" + e.getMessage());
        } finally {
            try {

                if (fos != null) {
                    fos.close();
                } 
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
前端
点击查看代码

// 导出占位符word
export function exportZwfWord (url,params,id,callback){
    exportDataExcel(url,params,{
        header:{ },responseType:"blob"
    }).then(response=>{

        if (response.status==200){
            let fileName = response.headers["content-disposition"].split(";")[1].split("filename=")[1];
            const aLink = document.createElement("a");
            document.body.appendChild(aLink);
            aLink.style.display = "none";
            const objectUrl = window.URL.createObjectURL(new Blob([response.data], { type: 'application/docx; charset=utf-8' }));
            aLink.href = objectUrl;
            aLink.download = decodeURI(fileName);
            aLink.click();
            document.body.removeChild(aLink);
            message.success('导出成功')
            callback(response.data);
            return;
        }else{
            if (response.message) {
                message.error(response.message);
            } else {
                message.error("没有数据");
            }
            callback();
        }
    })
}

遇到问题:
1。Java使用POI技术导出Word文档,报错Package should contain a content type part [M1.13]
解决方案:
因为XWPFDocument只支持docx,所以需要将doc文件另存为 docx. 切记不能直接改后缀,否则不生效的。
image
2。问题2:response的Content-Disposition写文件名要与前端对应,否则,导出undefined.

效果:
image

image

下面增加补充自动增加表格行:

点击查看代码
package com.ly.education.trainingResource.server.service.impl;

import com.ly.education.commons.util.Encodes;
import com.ly.education.trainingResource.api.dto.ReviewTemplateDto;
import com.ly.education.trainingResource.api.vo.ReviewTemplateVo;
import com.ly.education.trainingResource.server.mapper.WordPrintMapper;
import com.ly.education.trainingResource.server.service.WordPrintService;
import com.ly.spring.boot.uuid.UUIDStringGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 项目管理_评审模板  ReviewTemplate
 *
 * @author xujinhui
 */
@Slf4j
@Service
public class WordPrintServiceImpl implements WordPrintService {

    @Autowired
    private WordPrintMapper wordPrintMapper;

    @Autowired
    private UUIDStringGenerator uuIDStringGenerator;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private HttpServletRequest request;

    /**
     * 字符串转换unicode
     *
     * @param string
     * @return
     */
    private static String stringToUnicode(String string) {
        StringBuffer unicode = new StringBuffer();
        for (int i = 0; i < string.length(); i++) {
            // 取出每一个字符
            char c = string.charAt(i);
            // 转换为unicode
            unicode.append("\\u" + Integer.toHexString(c));
        }

        return unicode.toString();
    }

    /**
     * 我们可以实现Java Word模板打印的功能。首先,我们需要引入Apache POI库,并创建一个Word模板文件。然后,我们需要读取模板文件并将其加载到内存中。
     * 接下来,我们需要遍历Word文档,找到包含占位符的位置,并将其替换为实际的内容。最后,我们可以将生成的Word文档导出为文件。
     * <p>
     * 在实际应用中,我们可以根据具体需求扩展这个功能,比如支持更复杂的模板替换、添加图片和表格等。同时,我们也可以使用其他的库来实现类似的功能,比如Docx4j等
     *
     * @param reviewTemplateDto 项目管理_评审模板  ReviewTemplate
     */
    @Override
    public void testWordPrint(ReviewTemplateDto reviewTemplateDto) {
        List<Map<String, Object>> list = wordPrintMapper.testWordPrint(reviewTemplateDto);


        // 创建Word模板文件:在开始代码编写之前,我们需要准备一个Word模板文件,模板文件中的文本可以是固定的,也可以使用占位符来表示需要动态填充的内容
        //读取Word模板文件
        // 我们需要使用Apache POI库来读取Word模板文件,并将其加载到内存中进行后续的操作。代码如下
        // 设置文件存储路径(全局根路径/相对路径)
//        String fileInPath = FilenameUtils
//                .normalize(getAttachmentBasePath().concat(File.separator).concat(relativePath));

        FileInputStream fis = null;
        try {
            File sPath = new File("D:/opt/jwxt/file/uploadfile/ly-edu-core-svc-thx/2.四川轻化工大学研究生教学成果奖申报简表(修改) - 副本.docx");
//            File sPath = new File("/template/testword.doc");
            fis = new FileInputStream(sPath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
//        HWPFDocument 支持doc  XWPFDocument支持docx
        XWPFDocument document = null;
        FileOutputStream fos = null;
        try {
            document = new XWPFDocument(fis);

            // 替换模板中的占位符
            //在模板文件中,我们可以使用占位符来表示需要动态填充的内容,比如使用${placeholder}来表示占位符。我们需要遍历Word文档的所有段落和表格,找到包含占位符的位置,并将其替换为实际的内容。代码如下

            List<XWPFParagraph> paragraphs = document.getParagraphs();
            for (XWPFParagraph paragraph : paragraphs) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    String text = run.getText(0);
                    if (text != null && text.contains("${{成果名称}}")) {
                        text = text.replace("${{成果名称}}", "替换后的内容");
                        run.setText(text, 0);
                    }
                }
            }

            // 表格处理方式
            List<XWPFTable> tables = document.getTables();

            // 添加行
            for (int pAddTabR = 0; pAddTabR < tables.size(); pAddTabR++) {
                XWPFTable table = tables.get(pAddTabR);

                // 计算表格最后一行的列值的占位符,用于 添加新行时,将占位符写进去
                List<String> cellZwfNames = new ArrayList<>();// 最后一行row的占位符的 名称
                List<XWPFTableCell> cells = table.getRows().get(table.getRows().size() - 1).getTableCells();
                for (int cellContInit = 0; cellContInit < cells.size(); cellContInit++) {
                    XWPFTableCell cell = cells.get(cellContInit);
                    String cellInitText = cell.getText();
//                    if (cellContInit == 0) {
                    if (StringUtils.isNotBlank(cellInitText)) {
                        cellInitText = cellInitText.replaceAll("\\$\\{\\{", "").replaceAll("0}}", "");
//                                cellInitText = "${{" + cellInitText + cellContInit + "}}";
//                            cellInitText = cellInitText.substring(cellInitText.indexOf("${{")+3,cellInitText.lastIndexOf("}}"));
                        cellZwfNames.add(cellInitText); // 拼成 ${{姓名}} 占位符
                    }
                    if (StringUtils.isBlank(cellInitText)) {
                        cellZwfNames.add("-");
                    }
                }

                // 加新行
                int addTotalRows = 5;
                if (addTotalRows > 0) {
                    addWordTableRows(table, table.getRows().size() - 1, 5, table.getRows().size(), cellZwfNames);
                }
            }

            // 替换占位符为查询出来的值
            for (XWPFTable table : tables) {
                List<XWPFTableRow> rows = table.getRows();
                for (XWPFTableRow row : rows) {
                    List<XWPFTableCell> cells = row.getTableCells();
                    for (XWPFTableCell cell : cells) {
                        List<XWPFParagraph> cellParagraphs = cell.getParagraphs();
                        for (XWPFParagraph cellParagraph : cellParagraphs) {
                            List<XWPFRun> runs = cellParagraph.getRuns();
                            for (XWPFRun run : runs) {
                                String text = run.getText(0);
                                if (text != null && text.contains("${{完成人姓名1}}")) {
                                    text = text.replace("${{完成人姓名1}}", "替换后的内容");
                                    run.setText(text, 0);
                                }
                                if (text != null && text.contains("${{成果名称}}")) {
                                    text = text.replace("${{成果名称}}", "替换后的内容2222");
                                    run.setText(text, 0);
                                }
                            }
                        }
                    }
                }

            }

            // 导出word文档
//            File outFile = new File("D:\\opt\\jwxt\\file\\uploadfile\\ly-edu-core-svc-thx\\四川教学成果占位符完成打印.docx");// 直接写磁盘
//            fos = new FileOutputStream(outFile);
//
////            document.write(fos);//
            String filePath = request.getSession().getServletContext().getRealPath("/");
            String fileName = "导出word.docx";
//            response.setHeader("Content-Disposition", "attachment;filename=" + stringToUnicode(fileName));
            response.setHeader("Content-Disposition", "attachment;filename=" + Encodes.urlEncode(fileName));
            response.setContentType("multipart/form-data");

            OutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            document.write(outputStream);

        } catch (Exception e) {
            log.error("获取word文档失败,原因是:" + e.getMessage());
            e.printStackTrace();
        } finally {
            try {

                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * des:表末尾添加行(表,要复制样式的行,添加行数,插入的行下标索引)
     *
     * @param table
     * @param source
     * @param rows
     * @param cellZwfNames 新增的行的 cell内容 占位符也要自增
     */
    public void addWordTableRows(XWPFTable table, int source, int rows, int insertRowIndex, List<String> cellZwfNames) {
        try {
            //获取表格的总行数
            int index = table.getNumberOfRows();
            //循环添加行和和单元格
            for (int i = 1; i <= rows; i++) {
                //获取要复制样式的行
                XWPFTableRow sourceRow = table.getRow(source);
                //添加新行
                XWPFTableRow targetRow = table.insertNewTableRow(insertRowIndex++);
                //复制行的样式给新行
                targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
                //获取要复制样式的行的单元格
                List<XWPFTableCell> sourceCells = sourceRow.getTableCells();
                //循环复制单元格
                for (int newCellIndex = 0; newCellIndex < sourceCells.size(); newCellIndex++) {
                    XWPFTableCell sourceCell = sourceCells.get(newCellIndex);

                    //添加新列
                    XWPFTableCell newCell = targetRow.addNewTableCell();
                    //复制单元格的样式给新单元格
                    newCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
                    //设置垂直居中
                    newCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);//垂直居中
                    //复制单元格的居中方式给新单元格
                    CTPPr pPr = sourceCell.getCTTc().getPList().get(0).getPPr();
                    if (pPr != null && pPr.getJc() != null && pPr.getJc().getVal() != null) {
                        CTTc cttc = newCell.getCTTc();
                        CTP ctp = cttc.getPList().get(0);
                        CTPPr ctppr = ctp.getPPr();
                        if (ctppr == null) {
                            ctppr = ctp.addNewPPr();
                        }
                        CTJc ctjc = ctppr.getJc();
                        if (ctjc == null) {
                            ctjc = ctppr.addNewJc();
                        }
                        ctjc.setVal(pPr.getJc().getVal()); //水平居中
                    }

                    //得到复制单元格的段落
                    List<XWPFParagraph> sourceParagraphs = sourceCell.getParagraphs();
                    if (StringUtils.isEmpty(sourceCell.getText())) {
                        continue;
                    }
                    //拿到第一段
                    XWPFParagraph sourceParagraph = sourceParagraphs.get(0);
                    //得到新单元格的段落
                    List<XWPFParagraph> targetParagraphs = newCell.getParagraphs();
                    //判断新单元格是否为空
                    if (StringUtils.isEmpty(newCell.getText())) {
                        //添加新的段落
                        XWPFParagraph ph = newCell.addParagraph();
                        //复制段落样式给新段落
                        ph.getCTP().setPPr(sourceParagraph.getCTP().getPPr());
                        String str = sourceCell.getText();
                        //得到文本对象
                        XWPFRun run = ph.getRuns().isEmpty() ? ph.createRun() : ph.getRuns().get(0);
                        run.setText("${{" + cellZwfNames.get(newCellIndex) + i + "}}");
                        //复制文本样式
                        run.setFontFamily(sourceParagraph.getRuns().get(0).getFontFamily());
                    } else {
                        XWPFParagraph ph = targetParagraphs.get(0);
                        ph.getCTP().setPPr(sourceParagraph.getCTP().getPPr());
                        XWPFRun run = ph.getRuns().isEmpty() ? ph.createRun() : ph.getRuns().get(0);
                        run.setFontFamily(sourceParagraph.getRuns().get(0).getFontFamily());
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }


}

posted on   HeavenTang  阅读(699)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示