java導出word含表格并且帶圖片

背景

我們需要通過?Java?動態導出?Word 文檔,基于預定義的?模板文件(如?.docx?格式)。模板中包含?表格,程序需要完成以下操作:

  1. 替換模板中的文本(如占位符 ${設備類型}??等)。

  2. 替換模板中的圖片(如占位符 {{圖片_作業現場}}?)。

模板示例

模板文件(如?template.docx)結構大致如下:

maven依賴

<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version></dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version></dependency><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.1</version></dependency>

Controller

@ApiOperation(notes = "模板導出", value = "使用模板導出文檔")
@RequestMapping(value = "/exportByTemplate", method = RequestMethod.GET)
public void exportByTemplate(HttpServletResponse response) {try {// 1. 設置響應頭response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");response.setHeader("Content-Disposition", "attachment;filename=report.docx");// 2. 準備數據Map<String, Object> data = new HashMap<>();data.put("設備類型", "開關");data.put("屬地運維單位", "湘江公司");data.put("作業現場", new String[]{"D:\\upload\\upload\\2025\\04\\14\\20250414070702.jpg","D:\\upload\\upload\\2025\\04\\14\\20250414070720.jpg"});// 3. 調用生成方法pdPointProblemService.generateFromTemplate(response,"D:\\1.docx", // 模板路徑data);} catch (Exception e) {e.printStackTrace();// 異常處理(略)}
}

ServiceImpl

@Override
public void generateFromTemplate(HttpServletResponse response,String templatePath,Map<String, Object> data) throws Exception {// 1. 初始化文檔(不使用try-with-resources)FileInputStream fis = new FileInputStream(templatePath);XWPFDocument doc = new XWPFDocument(fis);try {// 2. 執行替換replaceText(doc, data);replaceImages(doc, data);OutputStream out = response.getOutputStream();doc.write(out);out.flush();} finally {if (fis != null) {fis.close();}}
}private void replaceText(XWPFDocument doc, Map<String, Object> data) {// 替換段落中的文本for (XWPFParagraph p : doc.getParagraphs()) {replaceTextInParagraph(p, data);}// 替換表格中的文本for (XWPFTable table : doc.getTables()) {for (XWPFTableRow row : table.getRows()) {for (XWPFTableCell cell : row.getTableCells()) {for (XWPFParagraph p : cell.getParagraphs()) {replaceTextInParagraph(p, data);}}}}
}private void replaceTextInParagraph(XWPFParagraph paragraph, Map<String, Object> data) {// 1. 合并段落內所有Run的文本String fullText = mergeAllRuns(paragraph);if (!fullText.contains("${")) return;// 2. 執行全局替換String newText = replacePlaceholders(fullText, data);// 3. 清空原有Run的文本(保留樣式)clearRunTexts(paragraph);// 4. 將新文本寫入第一個Run(保留原始格式)if (!paragraph.getRuns().isEmpty()) {XWPFRun firstRun = paragraph.getRuns().get(0);firstRun.setText(newText, 0);} else {paragraph.createRun().setText(newText);}
}/*** 正則替換完整文本*/
private String replacePlaceholders(String text, Map<String, Object> data) {Pattern pattern = Pattern.compile("\\$\\{(.+?)}");Matcher matcher = pattern.matcher(text);StringBuffer sb = new StringBuffer();while (matcher.find()) {String key = matcher.group(1);Object value = data.getOrDefault(key, "");matcher.appendReplacement(sb, Matcher.quoteReplacement(value.toString()));}matcher.appendTail(sb);return sb.toString();
}/*** 清空所有Run的文本(保留樣式)*/
private void clearRunTexts(XWPFParagraph paragraph) {for (XWPFRun run : paragraph.getRuns()) {run.setText("", 0); // 清空文本但保留Run對象}
}private void replaceImages(XWPFDocument doc, Map<String, Object> data) throws Exception {// 1. 處理普通段落for (XWPFParagraph p : doc.getParagraphs()) {processParagraphForImages(p, data);}// 2. 處理表格內的段落for (XWPFTable table : doc.getTables()) {for (XWPFTableRow row : table.getRows()) {for (XWPFTableCell cell : row.getTableCells()) {for (XWPFParagraph p : cell.getParagraphs()) {processParagraphForImages(p, data);}}}}
}/*** 統一處理段落中的圖片占位符*/
private void processParagraphForImages(XWPFParagraph p, Map<String, Object> data) throws Exception {// 合并段落內所有Run的文本String mergedText = mergeAllRuns(p);if (mergedText.isEmpty()) return;// 正則匹配圖片占位符Matcher matcher = Pattern.compile("\\{\\{圖片_(.+?)}}").matcher(mergedText);if (!matcher.find()) return;String placeholder = matcher.group(0);String fieldName = matcher.group(1);// 清理占位符clearPlaceholderRuns(p, placeholder);// 插入圖片if (data.containsKey(fieldName)) {
//            String imagePath = (String) data.get(fieldName);
//            insertImage(p, imagePath);String[] imageList  = (String[]) data.get(fieldName);insertImageList(p,imageList);}
}private void insertImageList(XWPFParagraph paragraph, String[] imagePaths) throws Exception {for (String imagePath : imagePaths) {File imageFile = new File(imagePath);if (!imageFile.exists()) {System.out.println("圖片文件不存在: " + imagePath);}FileInputStream fis = new FileInputStream(imageFile);byte[] bytes = IOUtils.toByteArray(fis);fis.close();int format = getImageFormat(imagePath);// 添加圖片到文檔中,返回的是圖片IDString blipId = paragraph.getDocument().addPictureData(bytes, format);// 創建圖片關聯的 CTDrawingint id = paragraph.getDocument().getNextPicNameNumber(format);XWPFRun run = paragraph.createRun();int width = 300; // pxint height = 200; // pxint widthEmu = Units.toEMU(width);int heightEmu = Units.toEMU(height);String picXml = getPicXml(blipId, widthEmu, heightEmu, id);// 讀取為 CTInlineCTInline inline = run.getCTR().addNewDrawing().addNewInline();XmlToken xmlToken = XmlToken.Factory.parse(picXml);inline.set(xmlToken);// 設置圖片的大小和描述inline.setDistT(0);inline.setDistB(0);inline.setDistL(0);inline.setDistR(0);CTPositiveSize2D extent = inline.addNewExtent();extent.setCx(widthEmu);extent.setCy(heightEmu);CTNonVisualDrawingProps docPr = inline.addNewDocPr();docPr.setId(id);docPr.setName("圖片_" + id);docPr.setDescr("描述_" + id);// 可選:圖片之間加個換行run.addBreak();}
}/*** 合并段落內所有Run的文本*/
private String mergeAllRuns(XWPFParagraph paragraph) {StringBuilder sb = new StringBuilder();for (XWPFRun run : paragraph.getRuns()) {String text = run.getText(0);if (text != null) {sb.append(text);}}return sb.toString();
}/*** 處理占位符跨多個Run的情況,并刪除相關Run*/
private void clearPlaceholderRuns(XWPFParagraph paragraph, String placeholder) {List<XWPFRun> runs = paragraph.getRuns();if (runs == null || runs.isEmpty()) {return;}StringBuilder allText = new StringBuilder();List<Integer> runPositions = new ArrayList<>();// 收集每個run的起始位置for (XWPFRun run : runs) {runPositions.add(allText.length());String text = run.getText(0);if (text != null) {allText.append(text);}}String fullText = allText.toString();int startIndex = fullText.indexOf(placeholder);if (startIndex == -1) {return; // 找不到占位符,不處理}int endIndex = startIndex + placeholder.length();// 找到涉及到的 run 范圍int runStart = -1;int runEnd = -1;for (int i = 0; i < runPositions.size(); i++) {int runPos = runPositions.get(i);if (runStart == -1 && runPos <= startIndex && (i == runPositions.size() - 1 || runPositions.get(i + 1) > startIndex)) {runStart = i;}if (runPos <= endIndex && (i == runPositions.size() - 1 || runPositions.get(i + 1) >= endIndex)) {runEnd = i;break;}}// 刪除 run,注意:從后往前刪,避免下標錯亂for (int i = runEnd; i >= runStart; i--) {paragraph.removeRun(i);}
}/*** 獲取圖片格式類型*/
private int getImageFormat(String fileName) {String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();switch (extension) {case "jpg":case "jpeg": return XWPFDocument.PICTURE_TYPE_JPEG;case "png":  return XWPFDocument.PICTURE_TYPE_PNG;default:     return XWPFDocument.PICTURE_TYPE_JPEG;}
}private static String getPicXml(String blipId, int widthEmu, int heightEmu, int id) {return"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" +"   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +"      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +"         <pic:nvPicPr>" +"            <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" +"            <pic:cNvPicPr/>" +"         </pic:nvPicPr>" +"         <pic:blipFill>" +"            <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +"            <a:stretch><a:fillRect/></a:stretch>" +"         </pic:blipFill>" +"         <pic:spPr>" +"            <a:xfrm>" +"               <a:off x=\"0\" y=\"0\"/>" +"               <a:ext cx=\"" + widthEmu + "\" cy=\"" + heightEmu + "\"/>" +"            </a:xfrm>" +"            <a:prstGeom prst=\"rect\">" +"               <a:avLst/>" +"            </a:prstGeom>" +"         </pic:spPr>" +"      </pic:pic>" +"   </a:graphicData>" +"</a:graphic>";
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77410.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77410.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77410.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Oracle19C低版本一天遭遇兩BUG(ORA-04031/ORA-600)

昨天幫朋友看一個系統異常卡頓的案例&#xff0c;在這里分享給大家 環境&#xff1a;Exadata X8M 數據庫版本19.11 1.系統報錯信息 表象為系統卡頓&#xff0c;頁面無法刷出&#xff0c;登陸到主機上看到節點1 系統等待存在大量的 cursor: pin S wait on X等待 查看兩個節…

2025年Q1數據安全政策、規范、標準以及報告匯總共92份(附下載)

一、政策演進趨勢分析 &#xff08;一&#xff09;國家級政策新動向 數據要素市場建設 數據流通安全治理方案&#xff08;重點解析數據確權與交易規則&#xff09; 公共數據授權運營規范&#xff08;創新性提出分級授權機制&#xff09; 新興技術安全規范 人工智能安全標準…

ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss

場景復現&#xff1a; 最近在vue3項目中安裝了tailwindcss&#xff0c;但是它默認幫我安裝的版本是4XX的&#xff0c;導致我執行 npx tailwindcss init -p報錯了。 解決方案&#xff1a; 更改tailwindcss的版本為3 pnpm add -D tailwindcss3再次執行生成tailwindcss的初始…

第 4 篇:Motion 拖拽與手勢動畫(交互篇)—— 打造直覺化交互體驗

Framer Motion 的拖拽與手勢系統讓實現復雜交互變得異常簡單。本文將深入解析核心 API&#xff0c;并通過實戰案例演示如何創造自然流暢的交互體驗。 &#x1f9f2; 拖拽動畫基礎 1. 啟用拖拽 使用 drag 屬性即可開啟拖拽能力。支持的值有&#xff1a;true&#xff08;全方向…

CF148D Bag of mice

題目傳送門 思路 狀態設計 設 d p i , j dp_{i, j} dpi,j? 表示袋中有 i i i 個白鼠和 j j j 個黑鼠時&#xff0c; A A A 能贏的概率。 狀態轉移 現在考慮抓鼠情況&#xff1a; A A A 抓到白鼠&#xff1a;直接判 A A A 贏&#xff0c;概率是 i i j \frac{i}{i j}…

BT1120 BT656驅動相關代碼示例

前些年做視頻輸出項目的時候用過bt1120 tx與rx模塊&#xff0c;現將部分代碼進行記錄整理。代碼功能正常&#xff0c;可正常應用。 1. rx部分&#xff1a; /****************************************************************************** Copyright (C) 2021,All rights …

服務器簡介(含硬件外觀接口介紹)

服務器&#xff08;Server&#xff09;是指提供資源、服務、數據或應用程序的計算機系統或設備。它通常比普通的個人計算機更強大、更可靠&#xff0c;能夠長時間無間斷運行&#xff0c;支持多個用戶或客戶端的請求。簡單來說&#xff0c;服務器就是專門用來存儲、管理和提供數…

SQL-exists和in核心區別?、 性能對比?、適用場景?

EXISTS和IN的基本區別。IN用于檢查某個值是否在子查詢返回的結果集中,而EXISTS用于檢查子 查詢是否至少返回了一行數據。通常來說,EXISTS在子查詢結果集較大時表現更好,因為一旦找 到匹配項就會停止搜索,而IN則需要遍歷整個結果集。 在 SQL 中,EXISTS 和 IN 都可以用于…

煥活身心,解鎖健康養生新方式

健康養生是一門科學&#xff0c;更是一種生活智慧。從日常點滴做起&#xff0c;才能筑牢健康根基。? 飲食上&#xff0c;應遵循 “食物多樣&#xff0c;谷類為主” 原則。多攝入新鮮蔬果&#xff0c;它們富含維生素與膳食纖維&#xff0c;有助于增強免疫力&#xff1b;選擇全…

QT+Cmake+mingw32-make編譯64位的zlib-1.3.1源碼成功過程

由于開源的軟件zlib庫是很多相關庫libpng等基礎庫&#xff0c;因此掌握使用mingw編譯器來編譯zlib源碼的步驟十分重要。本文主要是通過圖文模式講解完整的qtcmakezlib源碼搭建和測試過程&#xff0c;為后續的其他源碼編譯環境搭建做基礎準備。 詳細步驟如下&#xff1a; 1、下…

健身會員管理系統(ssh+jsp+mysql8.x)含運行文檔

健身會員管理系統(sshjspmysql8.x) 對健身房的健身器材、會員、教練、辦卡、會員健身情況進行管理&#xff0c;可根據會員號或器材進行搜索&#xff0c;查看會員健身情況或器材使用情況。

【langchain4j】Springboot如何接入大模型以及實戰開發-AI問答助手(一)

langchain4j介紹 官網地址&#xff1a;https://docs.langchain4j.dev/get-started langchain4j可以說是java和spring的關系&#xff0c;spring讓我們開發java應用非常簡單&#xff0c;那么langchain4j對應的就是java開發ai的 “Spring” 他集成了AI應用的多種場景&#xff0c…

平均池化(Average Pooling)

1. 定義與作用?? ??平均池化??是一種下采樣操作&#xff0c;通過對輸入區域的數值取??平均值??來壓縮數據空間維度。其核心作用包括&#xff1a; ??降低計算量??&#xff1a;減少特征圖尺寸&#xff0c;提升模型效率。??保留整體特征??&#xff1a;平滑局部…

【dify實戰】chatflow結合deepseek實現基于自然語言的數據庫問答、Echarts可視化展示、Excel報表下載

dify結合deepseek實現基于自然語言的數據庫問答、Echarts可視化展示、Excel報表下載 觀看視頻&#xff0c;您將學會 在dify下如何快速的構建一個chatflow&#xff0c;來完成數據分析工作&#xff1b;如何在AI的回復中展示可視化的圖表&#xff1b;如何在AI 的回復中加入Excel報…

加一:從簡單問題到復雜邊界的深度思考

加一&#xff1a;從簡單問題到復雜邊界的深度思考 引言 在算法世界里&#xff0c;有些問題看似簡單&#xff0c;實則暗藏玄機&#xff0c;其中“加一”問題就是一個典型例子。所謂“加一”&#xff0c;通常指的是給一個由數字組成的數組表示的整數加一&#xff0c;這聽起來簡…

PointCore——利用局部全局特征的高效無監督點云異常檢測器論文與算法解讀

概述 三維點云異常檢測旨在從訓練集中檢測出異常數據點&#xff0c;是工業檢測、自動駕駛等眾多應用的基礎。然而&#xff0c;現有的點云異常檢測方法通常采用多個特征存儲庫來充分保留局部和全局特征表示&#xff0c;這帶來了高昂的計算成本以及特征之間的不匹配問題。為解決…

桌面應用UI開發方案

一、基于 Web 技術的跨平臺方案 Electron Python/Go 特點&#xff1a; 技術棧&#xff1a;前端使用 HTML/CSS/JS&#xff0c;后端通過 Node.js 集成 Python/Go 模塊或服務。 跨平臺&#xff1a;支持 Windows、macOS、Linux 桌面端&#xff0c;適合開發桌面應用。 生態成熟&…

redis 配置日志和數據存儲位置

Redis配置日志和數據存儲位置 介紹 Redis是一個開源的高性能鍵值存儲數據庫&#xff0c;常用于緩存、消息隊列和實時分析等場景。在使用Redis時&#xff0c;我們需要配置日志和數據存儲位置&#xff0c;以便更好地管理和監控Redis的運行狀態。本文將介紹如何配置Redis的日志和數…

OSI七層網絡模型詳解

OSI七層網絡模型詳解 OSI&#xff08;開放系統互連&#xff09;模型是國際標準化組織&#xff08;ISO&#xff09;提出的網絡通信框架&#xff0c;旨在規范不同系統間的通信。它分為七層&#xff0c;每層承擔特定功能&#xff0c;協同實現端到端的數據傳輸。 1. 物理層&#x…

Springboot 學習 之 logback-spring.xml 日志打印

文章目錄 1. property2. springProperty3. appender4. logger4.1. 通過包路徑控制日志4.2. 通過類名控制日志4.3. 按自定義 Logger 名稱控制日志 5. root6. springProfile SpringBoot 項目中可以通過自定義 logback-spring.xml 中各項配置&#xff0c;實現日志的打印控制 1. p…