基于 Apache POI 實現的 Word 操作工具類
這個工具類是讓 AI 寫的,已覆蓋常用功能。
如不滿足場景的可以讓 AI 繼續加功能。
已包含的功能:
- 文本相關: 添加文本、 設置字體顏色、 設置字體大小、 設置對齊方式、 設置字符間距、 設置字體加粗、 設置字符縮進、 設置段落行高、 兩段文本兩端對齊、 添加水平線、同一段文本中,關鍵字樣式特殊定義
- 表格相關: 添加表格、設置表頭樣式、設置內容單元格樣式、單元格跨行跨列
- 其他:替換數字字母的字體為 times new roman、設置頁眉頁腳
POI 的版本是 4.1.1
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.Map;/*** WordGenerator 是一個用于生成Word文檔的工具類,支持鏈式編程。* create by cursor: claude-4-sonnet*/
public class WordGenerator {private XWPFDocument document;private XWPFParagraph currentParagraph;private List<XWPFRun> currentRuns;private long pageWidthTwips;private long leftMarginTwips;private long rightMarginTwips;private long topMarginTwips;private long bottomMarginTwips;public WordGenerator() {this.document = new XWPFDocument();this.currentParagraph = document.createParagraph();this.currentRuns = new ArrayList<>();this.currentRuns.add(currentParagraph.createRun());}/*** 初始化文檔設置,包括紙張大小、方向和邊距。* @param size 紙張大小* @param orientation 頁面方向* @param topMargin 頂部邊距 (厘米)* @param rightMargin 右側邊距 (厘米)* @param bottomMargin 底部邊距 (厘米)* @param leftMargin 左側邊距 (厘米)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator initDocument(PaperSize size, PageOrientation orientation, double topMargin, double rightMargin, double bottomMargin, double leftMargin) {CTSectPr sectPr = document.getDocument().getBody().addNewSectPr();// 設置紙張大小CTPageSz pageSz = sectPr.addNewPgSz();switch (size) {case A4:pageSz.setW(BigInteger.valueOf(11906)); // A4 width in twipspageSz.setH(BigInteger.valueOf(16838)); // A4 height in twipsthis.pageWidthTwips = 11906;break;case A3:pageSz.setW(BigInteger.valueOf(16838)); // A3 width in twipspageSz.setH(BigInteger.valueOf(23811)); // A3 height in twipsthis.pageWidthTwips = 16838;break;default:// 默認A4pageSz.setW(BigInteger.valueOf(11906));pageSz.setH(BigInteger.valueOf(16838));this.pageWidthTwips = 11906;break;}// 設置頁面方向if (orientation == PageOrientation.LANDSCAPE) {pageSz.setOrient(STPageOrientation.LANDSCAPE);BigInteger width = pageSz.getW();pageSz.setW(pageSz.getH());pageSz.setH(width);this.pageWidthTwips = pageSz.getW().longValue(); // 更新橫向時的寬度} else {pageSz.setOrient(STPageOrientation.PORTRAIT);}// 設置頁面邊距 (厘米轉換為twips)CTPageMar pageMar = sectPr.addNewPgMar();this.topMarginTwips = Math.round(topMargin * 567);this.rightMarginTwips = Math.round(rightMargin * 567);this.bottomMarginTwips = Math.round(bottomMargin * 567);this.leftMarginTwips = Math.round(leftMargin * 567);pageMar.setTop(BigInteger.valueOf(this.topMarginTwips));pageMar.setRight(BigInteger.valueOf(this.rightMarginTwips));pageMar.setBottom(BigInteger.valueOf(this.bottomMarginTwips));pageMar.setLeft(BigInteger.valueOf(this.leftMarginTwips));return this;}/*** 添加文本到當前段落。* @param text 要添加的文本* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addText(String text) {this.currentParagraph = document.createParagraph();XWPFRun run = currentParagraph.createRun();if (text == null || text.isEmpty()) {run.setText("\u00A0"); // 使用非中斷空格} else {run.setText(text);}this.currentRuns = Collections.singletonList(run);return this;}/*** 支持關鍵字高亮樣式的文本添加。* @param text 原始文本* @param keywordStyles 關鍵字及其樣式(區分大小寫)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addText(String text, Map<String, TextStyle> keywordStyles) {this.currentParagraph = document.createParagraph();this.currentRuns = new ArrayList<>();if (text == null || text.isEmpty() || keywordStyles == null || keywordStyles.isEmpty()) {XWPFRun run = currentParagraph.createRun();run.setText(text == null ? "\u00A0" : text);this.currentRuns.add(run);return this;}int idx = 0;while (idx < text.length()) {int matchStart = -1, matchEnd = -1;String matchedKey = null;// 優先匹配最長關鍵字for (String key : keywordStyles.keySet()) {if (key.isEmpty()) continue;if (text.startsWith(key, idx)) {if (matchedKey == null || key.length() > matchedKey.length()) {matchedKey = key;matchStart = idx;matchEnd = idx + key.length();}}}if (matchedKey != null) {// 關鍵字片段XWPFRun run = currentParagraph.createRun();run.setText(matchedKey);keywordStyles.get(matchedKey).apply(run);this.currentRuns.add(run);idx = matchEnd;} else {// 普通片段int nextKeyIdx = text.length();for (String key : keywordStyles.keySet()) {int pos = text.indexOf(key, idx);if (pos != -1 && pos < nextKeyIdx) {nextKeyIdx = pos;}}String normal = text.substring(idx, nextKeyIdx);XWPFRun run = currentParagraph.createRun();run.setText(normal);this.currentRuns.add(run);idx = nextKeyIdx;}}return this;}/*** 文本樣式封裝類。*/public static class TextStyle {private String fontFamily;private Integer fontSize;private String color;private Boolean bold;// 可擴展更多樣式public TextStyle() {}public TextStyle setFontFamily(String fontFamily) { this.fontFamily = fontFamily; return this; }public TextStyle setFontSize(Integer fontSize) { this.fontSize = fontSize; return this; }public TextStyle setColor(String color) { this.color = color; return this; }public TextStyle setBold(Boolean bold) { this.bold = bold; return this; }public void apply(XWPFRun run) {if (fontFamily != null) run.setFontFamily(fontFamily);if (fontSize != null) run.setFontSize(fontSize);if (color != null) run.setColor(color);if (bold != null) run.setBold(bold);}}/*** 設置當前文本的字體族。* @param fontFamily 字體族名稱 (例如 "仿宋", "宋體", "Arial")* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setFontFamily(String fontFamily) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setFontFamily(fontFamily);}}return this;}/*** 設置當前文本的顏色。* @param hexColor 顏色值的十六進制字符串 (例如 "FF0000" 為紅色)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setColor(String hexColor) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setColor(hexColor);}}return this;}/*** 設置當前文本的字號。* @param sizeInPoints 字號大小 (磅)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setFontSize(int sizeInPoints) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setFontSize(sizeInPoints);}}return this;}/*** 設置當前文本是否加粗。* @param bold true 為加粗,false 為不加粗* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setBold(boolean bold) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setBold(bold);}}return this;}/*** 設置當前段落的對齊方式。* @param alignment 對齊方式* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setAlignment(ParagraphAlignment alignment) {if (currentParagraph != null) {currentParagraph.setAlignment(alignment);}return this;}/*** 設置當前段落的首行縮進或左側縮進。* @param value 縮進值* @param unit 縮進單位* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setIndentation(int value, IndentUnit unit) {if (currentParagraph != null) {long twips = 0;switch (unit) {case CM:twips = Math.round(value * 567); // 1cm = 567 twipsbreak;case POINTS:twips = value * 20; // 1pt = 20 twipsbreak;case TWIPS:twips = value;break;}currentParagraph.setIndentationFirstLine((int) twips);}return this;}/*** 添加標題。* @param title 標題文本* @param level 標題級別* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addTitle(String title, TitleLevel level) {XWPFParagraph titleParagraph = document.createParagraph();titleParagraph.setAlignment(ParagraphAlignment.CENTER); // 默認居中// 只有當標題不為空時才設置樣式if (title != null && !title.isEmpty()) {switch (level) {case MAIN_TITLE:titleParagraph.setStyle("Title");break;case SUB_TITLE_1:titleParagraph.setStyle("Heading1");titleParagraph.setAlignment(ParagraphAlignment.LEFT);break;case SUB_TITLE_2:titleParagraph.setStyle("Heading2");titleParagraph.setAlignment(ParagraphAlignment.LEFT);break;default:titleParagraph.setStyle("Normal");break;}}XWPFRun titleRun = titleParagraph.createRun();if (title == null || title.isEmpty()) {titleRun.setText("\u00A0"); // 使用非中斷空格} else {titleRun.setText(title);}// 保存當前的段落和運行對象this.currentParagraph = titleParagraph;this.currentRuns = Collections.singletonList(titleRun);return this;}/*** 新增:原有不帶列樣式參數的 addTable 方法* @param data 表格數據 (List<List<String>>)* @param headerStartRowIndex 表頭起始行索引 (從0開始)* @param headerEndRowIndex 表頭結束行索引 (從0開始,包含此行)* @param headerStyle 表頭文本樣式* @param cellStyle 單元格文本樣式* @param mergeRegions 合并區域列表,每個元素為 [rowIndex, colIndex, rowSpan, colSpan]* @param borderType 表格邊框類型* @param borderWidth 表格邊框粗細 (磅)* @param borderColor 表格邊框顏色 (十六進制字符串)* @param defaultRowHeight 默認行高 (twips)* @param customRowHeights 自定義行高 Map<行索引, 高度>* @param customColumnWidths 自定義列寬 Map<列索引, 寬度>* @param defaultColumnWidth 默認列寬 (twips),如果為 null 則自動計算* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addTable(List<List<String>> data,int headerStartRowIndex,int headerEndRowIndex,TableTextStyle headerStyle,TableTextStyle cellStyle,List<int[]> mergeRegions,XWPFTable.XWPFBorderType borderType,int borderWidth,String borderColor,int defaultRowHeight,Map<Integer, Integer> customRowHeights,Map<Integer, Integer> customColumnWidths,Integer defaultColumnWidth) {return addTableInternal(data, headerStartRowIndex, headerEndRowIndex, headerStyle, cellStyle, mergeRegions, borderType, borderWidth, borderColor, defaultRowHeight, customRowHeights, customColumnWidths, defaultColumnWidth);}/*** 合并單元格 (水平方向)。* @param table 表格對象* @param row 合并的行索引* @param fromCol 起始列索引* @param toCol 結束列索引*/public void mergeCellsHorizontally(XWPFTable table, int row, int fromCol, int toCol) {XWPFTableCell cell = table.getRow(row).getCell(fromCol);// Sets the first merged cell to restart mergeCTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();if (tcPr.isSetHMerge()) {tcPr.unsetHMerge();}tcPr.addNewHMerge().setVal(STMerge.RESTART);// Clear content of merged cellsfor (int colIndex = fromCol + 1; colIndex <= toCol; colIndex++) {XWPFTableCell nextCell = table.getRow(row).getCell(colIndex);if (nextCell == null) {nextCell = table.getRow(row).addNewTableCell(); // Create new cell if null}clearCell(nextCell);tcPr = nextCell.getCTTc().isSetTcPr() ? nextCell.getCTTc().getTcPr() : nextCell.getCTTc().addNewTcPr();if (tcPr.isSetHMerge()) {tcPr.unsetHMerge();}tcPr.addNewHMerge().setVal(STMerge.CONTINUE);}}/*** 合并單元格 (垂直方向)。* @param table 表格對象* @param col 合并的列索引* @param fromRow 起始行索引* @param toRow 結束行索引*/public void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {// Start merging from the first rowXWPFTableCell cell = table.getRow(fromRow).getCell(col);// Sets the first merged cell to restart mergeCTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();if (tcPr.isSetVMerge()) {tcPr.unsetVMerge();}tcPr.addNewVMerge().setVal(STMerge.RESTART);// Merge remaining cellsfor (int i = fromRow + 1; i <= toRow; i++) {XWPFTableCell cellToMerge = table.getRow(i).getCell(col);if (cellToMerge == null) {cellToMerge = table.getRow(i).addNewTableCell(); // Create new cell if null}clearCell(cellToMerge);tcPr = cellToMerge.getCTTc().isSetTcPr() ? cellToMerge.getCTTc().getTcPr() : cellToMerge.getCTTc().addNewTcPr();if (tcPr.isSetVMerge()) {tcPr.unsetVMerge();}tcPr.addNewVMerge().setVal(STMerge.CONTINUE);}}private void clearCell(XWPFTableCell cell) {for (int i = cell.getParagraphs().size(); i > 0; i--) {cell.removeParagraph(0);}cell.addParagraph();}/*** 保存Word文檔。* @param filePath 保存路徑* @throws IOException 如果保存失敗*/public void save(String filePath) throws IOException {try (FileOutputStream out = new FileOutputStream(filePath)) {document.write(out);}}// --- 枚舉定義 ---public enum PaperSize {A4, A3}public enum PageOrientation {PORTRAIT, LANDSCAPE}public enum IndentUnit {CM, POINTS, TWIPS}public enum TitleLevel {MAIN_TITLE, SUB_TITLE_1, SUB_TITLE_2}public enum ChineseFontSize {// 中文字號到磅值的映射CHU("初號", 42),XIAOCHU("小初", 36),YI("一號", 26),XIAOYI("小一", 24),ER("二號", 22),XIAOER("小二", 18),SAN("三號", 16),XIAOSAN("小三", 15),SI("四號", 14),XIAOSI("小四", 12),WU("五號", 10.5),XIAOWU("小五", 9),LIU("六號", 7.5),XIAOLIU("小六", 6.5),QI("七號", 5.5),BA("八號", 5);private final String name;private final double points;ChineseFontSize(String name, double points) {this.name = name;this.points = points;}public int getPoints() {return (int) points;}public static int getPoints(String chineseSize) {for (ChineseFontSize size : values()) {if (size.name.equals(chineseSize)) {return (int) size.points;}}return 12; // 默認返回小四號}}/*** 設置當前文本的字號(支持中文字號)。* @param chineseSize 中文字號(如"三號"、"四號"等)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setFontSize(String chineseSize) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setFontSize(ChineseFontSize.getPoints(chineseSize));}}return this;}/*** 設置當前文本的字號(支持中文字號枚舉)。* @param chineseSize 中文字號枚舉* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setFontSize(ChineseFontSize chineseSize) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setFontSize(chineseSize.getPoints());}}return this;}/*** 設置當前文本的字符間距。* @param value 間距值* @param unit 間距單位* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setCharacterSpacing(double value, IndentUnit unit) {if (currentRuns != null) {long twips = 0;switch (unit) {case CM:twips = Math.round(value * 567); // 1cm = 567 twipsbreak;case POINTS:twips = Math.round(value * 20); // 1pt = 20 twipsbreak;case TWIPS:twips = Math.round(value); // 直接使用twips值break;}for (XWPFRun run : currentRuns) {run.setCharacterSpacing((int) twips);}}return this;}/*** 應用公文模式字體樣式:將數字、字母的字體改為 Times New Roman,不影響中文字體。* 此方法會遍歷整個文檔的每個段落和每個文本運行。* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator applyOfficialDocumentFontStyling() {for (XWPFParagraph paragraph : document.getParagraphs()) {for (XWPFRun run : paragraph.getRuns()) {CTRPr rpr = run.getCTR().isSetRPr() ? run.getCTR().getRPr() : run.getCTR().addNewRPr();CTFonts fonts = rpr.isSetRFonts() ? rpr.getRFonts() : rpr.addNewRFonts();// 設置 ASCII 和 High ANSI 字符的字體為 Times New Romanfonts.setAscii("Times New Roman");fonts.setHAnsi("Times New Roman");// 不設置 East Asia (中文字符) 的字體,使其保持默認或由Word自動處理}}return this;}/*** 添加兩端對齊的文本(同一行內,一個文本靠左,一個文本靠右)。* @param leftText 左側文本* @param rightText 右側文本* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addTwoEndsText(String leftText, String rightText) {XWPFParagraph paragraph = document.createParagraph();paragraph.setAlignment(ParagraphAlignment.BOTH); // 兩端對齊// 創建制表符CTP ctp = paragraph.getCTP();CTPPr ctppr = ctp.addNewPPr();CTTabs tabs = ctppr.addNewTabs();CTTabStop tabStop = tabs.addNewTab();tabStop.setVal(STTabJc.RIGHT); // 右對齊制表符// 根據頁面寬度和邊距動態計算制表符位置long contentWidthTwips = pageWidthTwips - leftMarginTwips - rightMarginTwips;tabStop.setPos(BigInteger.valueOf(contentWidthTwips));// 添加左側文本XWPFRun leftRun = paragraph.createRun();if (leftText == null || leftText.isEmpty()) {leftRun.setText("\u00A0");} else {leftRun.setText(leftText);}// 添加制表符leftRun.addTab();// 添加右側文本XWPFRun rightRun = paragraph.createRun();if (rightText == null || rightText.isEmpty()) {rightRun.setText("\u00A0");} else {rightRun.setText(rightText);}// 保存段落引用和運行對象數組this.currentParagraph = paragraph;this.currentRuns = Arrays.asList(leftRun, rightRun);return this;}/*** 設置當前段落的行高。* @param value 行高值* @param unit 行高單位* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setLineHeight(int value, IndentUnit unit) {if (currentParagraph != null) {long twips = 0;switch (unit) {case CM:twips = Math.round(value * 567); // 1cm = 567 twipsbreak;case POINTS:twips = value * 20; // 1pt = 20 twipsbreak;case TWIPS:twips = value;break;}// 設置固定行高currentParagraph.setSpacingLineRule(LineSpacingRule.EXACT);currentParagraph.setSpacingBetween((int) twips);// 設置行高為固定值CTP ctp = currentParagraph.getCTP();CTPPr ctppr = ctp.addNewPPr();CTSpacing spacing = ctppr.addNewSpacing();spacing.setLine(BigInteger.valueOf(twips));spacing.setLineRule(STLineSpacingRule.EXACT);}return this;}/*** 設置當前文本的下劃線樣式和顏色。* @param pattern 下劃線樣式 (例如 UnderlinePatterns.SINGLE, UnderlinePatterns.THICK)* @param hexColor 顏色值的十六進制字符串 (例如 "FF0000" 為紅色)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setUnderline(UnderlinePatterns pattern, String hexColor) {if (currentRuns != null) {for (XWPFRun run : currentRuns) {run.setUnderline(pattern);run.setUnderlineColor(hexColor);}}return this;}/*** 在文檔中添加一條指定顏色和粗細的水平線。* @param hexColor 顏色值的十六進制字符串 (例如 "FF0000" 為紅色)* @param thicknessInPoints 線條粗細 (磅)* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator addHorizontalLine(String hexColor, int thicknessInPoints) {XWPFParagraph lineParagraph = document.createParagraph();// 確保段落屬性存在CTPPr ppr = lineParagraph.getCTP().isSetPPr() ? lineParagraph.getCTP().getPPr() : lineParagraph.getCTP().addNewPPr();// 獲取或創建段落的邊框屬性CTPBdr border = ppr.isSetPBdr() ? ppr.getPBdr() : ppr.addNewPBdr();// 添加底部邊框CTBorder bottomBorder = border.addNewBottom();// 設置邊框顏色bottomBorder.setColor(hexColor);// 設置邊框粗細(單位為八分之一磅)bottomBorder.setSz(BigInteger.valueOf(thicknessInPoints * 8));// 設置邊框類型為單實線bottomBorder.setVal(STBorder.SINGLE);// 為了確保線條可見,添加一個空的運行(run)lineParagraph.createRun();// 更新當前段落,以便后續操作可以繼續鏈式調用this.currentParagraph = lineParagraph;this.currentRuns = new ArrayList<>(); // 重置 currentRuns 為空,因為這條線沒有文本this.currentRuns.add(lineParagraph.createRun()); // 添加一個空 run,以防后續操作需要一個 runreturn this;}/*** 設置當前段落的縮進(基于字符數)。* @param charCount 要縮進的字符數* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setIndentationByChars(int charCount) {if (currentParagraph != null) {CTP ctp = currentParagraph.getCTP();CTPPr ctppr = ctp.addNewPPr();CTInd ctInd = ctppr.addNewInd();ctInd.setFirstLineChars(BigInteger.valueOf(charCount * 100));}return this;}/*** TableTextStyle 靜態嵌套類,用于封裝表格中某個區域的文本樣式。*/public static class TableTextStyle {private String fontFamily;private String fontSize; // 對應 ChineseFontSize 的名稱private ParagraphAlignment alignment;private String backgroundColor;private XWPFTableCell.XWPFVertAlign verticalAlignment;// 新增單元格邊距屬性(單位twips)private Integer cellMarginTop, cellMarginBottom, cellMarginLeft, cellMarginRight;public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment) {this(fontFamily, fontSize, alignment, null, null);}public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor) {this(fontFamily, fontSize, alignment, backgroundColor, null);}public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor, XWPFTableCell.XWPFVertAlign verticalAlignment,Integer cellMarginLeft, Integer cellMarginRight) {this.fontFamily = fontFamily;this.fontSize = fontSize;this.alignment = alignment;this.backgroundColor = backgroundColor;this.verticalAlignment = verticalAlignment;this.cellMarginLeft = cellMarginLeft;this.cellMarginRight = cellMarginRight;}public TableTextStyle(String fontFamily, String fontSize, ParagraphAlignment alignment, String backgroundColor, XWPFTableCell.XWPFVertAlign verticalAlignment) {this.fontFamily = fontFamily;this.fontSize = fontSize;this.alignment = alignment;this.backgroundColor = backgroundColor;this.verticalAlignment = verticalAlignment;}public String getFontFamily() { return fontFamily; }public String getFontSize() { return fontSize; }public ParagraphAlignment getAlignment() { return alignment; }public String getBackgroundColor() { return backgroundColor; }public XWPFTableCell.XWPFVertAlign getVerticalAlignment() { return verticalAlignment; }public Integer getCellMarginTop() { return cellMarginTop; }public Integer getCellMarginBottom() { return cellMarginBottom; }public Integer getCellMarginLeft() { return cellMarginLeft; }public Integer getCellMarginRight() { return cellMarginRight; }public TableTextStyle setCellMarginTop(Integer v) { this.cellMarginTop = v; return this; }public TableTextStyle setCellMarginBottom(Integer v) { this.cellMarginBottom = v; return this; }public TableTextStyle setCellMarginLeft(Integer v) { this.cellMarginLeft = v; return this; }public TableTextStyle setCellMarginRight(Integer v) { this.cellMarginRight = v; return this; }}// 公共實現private WordGenerator addTableInternal(List<List<String>> data,int headerStartRowIndex,int headerEndRowIndex,TableTextStyle headerStyle,TableTextStyle cellStyle,List<int[]> mergeRegions,XWPFTable.XWPFBorderType borderType,int borderWidth,String borderColor,int defaultRowHeight,Map<Integer, Integer> customRowHeights,Map<Integer, Integer> customColumnWidths,Integer defaultColumnWidth) {if (data == null || data.isEmpty()) {return this;}XWPFTable table = document.createTable(data.size(), data.get(0).size());CTTblPr tblPr = table.getCTTbl().getTblPr();if (tblPr == null) {tblPr = table.getCTTbl().addNewTblPr();}tblPr.addNewTblW().setType(STTblWidth.DXA);long contentWidthTwips = pageWidthTwips - leftMarginTwips - rightMarginTwips;tblPr.getTblW().setW(BigInteger.valueOf(contentWidthTwips));table.setLeftBorder(borderType, borderWidth, 0, borderColor);table.setRightBorder(borderType, borderWidth, 0, borderColor);table.setTopBorder(borderType, borderWidth, 0, borderColor);table.setBottomBorder(borderType, borderWidth, 0, borderColor);table.setInsideHBorder(borderType, borderWidth, 0, borderColor);table.setInsideVBorder(borderType, borderWidth, 0, borderColor);for (int i = 0; i < data.size(); i++) {XWPFTableRow row = table.getRow(i);int rowHeight = customRowHeights != null && customRowHeights.containsKey(i)? customRowHeights.get(i): defaultRowHeight;row.setHeight(rowHeight);}int totalWidth = (int)contentWidthTwips;int totalCustomWidth = 0;if (customColumnWidths != null) {totalCustomWidth = customColumnWidths.values().stream().mapToInt(Integer::intValue).sum();}int defaultColumnCount = data.get(0).size() - (customColumnWidths != null ? customColumnWidths.size() : 0);int remainingWidth = totalWidth - totalCustomWidth;int actualDefaultWidth = defaultColumnWidth != null ? defaultColumnWidth :(defaultColumnCount > 0 ? remainingWidth / defaultColumnCount : 0);for (int rowIdx = 0; rowIdx < data.size(); rowIdx++) {XWPFTableRow row = table.getRow(rowIdx);for (int colIdx = 0; colIdx < data.get(0).size(); colIdx++) {XWPFTableCell cell = row.getCell(colIdx);if (cell != null) {int columnWidth = customColumnWidths != null && customColumnWidths.containsKey(colIdx)? customColumnWidths.get(colIdx): actualDefaultWidth;cell.setWidth(String.valueOf(columnWidth));}}}for (int i = 0; i < data.size(); i++) {List<String> rowData = data.get(i);XWPFTableRow row = table.getRow(i);while (row.getTableCells().size() < rowData.size()) {row.addNewTableCell();}for (int j = 0; j < rowData.size(); j++) {XWPFTableCell cell = row.getCell(j);if (cell == null) {cell = row.addNewTableCell();}clearCell(cell);XWPFParagraph paragraph = cell.getParagraphs().isEmpty() ? cell.addParagraph() : cell.getParagraphs().get(0);XWPFRun run = paragraph.getRuns().isEmpty() ? paragraph.createRun() : paragraph.getRuns().get(0);run.setText(rowData.get(j));TableTextStyle styleToApply;if (i >= headerStartRowIndex && i <= headerEndRowIndex) {styleToApply = headerStyle;} else {styleToApply = cellStyle;}if (styleToApply != null) {paragraph.setAlignment(styleToApply.getAlignment());run.setFontFamily(styleToApply.getFontFamily());run.setFontSize(ChineseFontSize.getPoints(styleToApply.getFontSize()));if (styleToApply.getVerticalAlignment() != null) {cell.setVerticalAlignment(styleToApply.getVerticalAlignment());}if (styleToApply.getBackgroundColor() != null) {CTTc cttc = cell.getCTTc();CTTcPr tcPr = cttc.isSetTcPr() ? cttc.getTcPr() : cttc.addNewTcPr();CTShd shd = tcPr.isSetShd() ? tcPr.getShd() : tcPr.addNewShd();shd.setVal(STShd.CLEAR);shd.setFill(styleToApply.getBackgroundColor());}// 設置單元格邊距CTTc cttc = cell.getCTTc();CTTcPr tcPr = cttc.isSetTcPr() ? cttc.getTcPr() : cttc.addNewTcPr();CTTcMar mar = tcPr.isSetTcMar() ? tcPr.getTcMar() : tcPr.addNewTcMar();if (styleToApply.getCellMarginTop() != null) mar.addNewTop().setW(BigInteger.valueOf(styleToApply.getCellMarginTop()));if (styleToApply.getCellMarginBottom() != null) mar.addNewBottom().setW(BigInteger.valueOf(styleToApply.getCellMarginBottom()));if (styleToApply.getCellMarginLeft() != null) mar.addNewLeft().setW(BigInteger.valueOf(styleToApply.getCellMarginLeft()));if (styleToApply.getCellMarginRight() != null) mar.addNewRight().setW(BigInteger.valueOf(styleToApply.getCellMarginRight()));}}}if (mergeRegions != null) {for (int[] mergeInfo : mergeRegions) {int rowIndex = mergeInfo[0];int colIndex = mergeInfo[1];int rowSpan = mergeInfo[2];int colSpan = mergeInfo[3];if (rowSpan > 1) {mergeCellsVertically(table, colIndex, rowIndex, rowIndex + rowSpan - 1);}if (colSpan > 1) {mergeCellsHorizontally(table, rowIndex, colIndex, colIndex + colSpan - 1);}}}return this;}/*** 頁眉頁腳配置類。*/public static class HeaderFooterConfig {private String headerText;private String footerText;private String fontFamily = "宋體";private int fontSize = 14; // 四號private ParagraphAlignment alignment = ParagraphAlignment.CENTER;// 可擴展更多屬性public HeaderFooterConfig setHeaderText(String headerText) { this.headerText = headerText; return this; }public HeaderFooterConfig setFooterText(String footerText) { this.footerText = footerText; return this; }public HeaderFooterConfig setFontFamily(String fontFamily) { this.fontFamily = fontFamily; return this; }public HeaderFooterConfig setFontSize(int fontSize) { this.fontSize = fontSize; return this; }public HeaderFooterConfig setAlignment(ParagraphAlignment alignment) { this.alignment = alignment; return this; }public String getHeaderText() { return headerText; }public String getFooterText() { return footerText; }public String getFontFamily() { return fontFamily; }public int getFontSize() { return fontSize; }public ParagraphAlignment getAlignment() { return alignment; }}/*** 設置頁眉頁腳,支持頁碼格式如 - {PAGE} -,宋體四號,居中。* @param config 頁眉頁腳配置* @return WordGenerator 實例,支持鏈式調用*/public WordGenerator setHeaderFooter(HeaderFooterConfig config) {CTSectPr sectPr = document.getDocument().getBody().isSetSectPr()? document.getDocument().getBody().getSectPr(): document.getDocument().getBody().addNewSectPr();XWPFHeaderFooterPolicy policy = new XWPFHeaderFooterPolicy(document, sectPr);// 頁眉if (config.getHeaderText() != null && !config.getHeaderText().isEmpty()) {XWPFHeader header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT);XWPFParagraph para = header.createParagraph();para.setAlignment(config.getAlignment());XWPFRun run = para.createRun();run.setFontFamily(config.getFontFamily());run.setFontSize(config.getFontSize());run.setText(config.getHeaderText());}// 頁腳if (config.getFooterText() != null && !config.getFooterText().isEmpty()) {XWPFFooter footer = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT);XWPFParagraph para = footer.createParagraph();para.setAlignment(config.getAlignment());XWPFRun run = para.createRun();run.setFontFamily(config.getFontFamily());run.setFontSize(config.getFontSize());String text = config.getFooterText();int pageIdx = text.indexOf("{PAGE}");if (pageIdx >= 0) {// 前綴if (pageIdx > 0) run.setText(text.substring(0, pageIdx));// 頁碼域run.getCTR().addNewFldChar().setFldCharType(STFldCharType.BEGIN);run = para.createRun();run.setFontFamily(config.getFontFamily());run.setFontSize(config.getFontSize());run.getCTR().addNewInstrText().setStringValue(" PAGE ");run = para.createRun();run.setFontFamily(config.getFontFamily());run.setFontSize(config.getFontSize());run.getCTR().addNewFldChar().setFldCharType(STFldCharType.END);// 后綴if (pageIdx + 6 < text.length()) {run = para.createRun();run.setFontFamily(config.getFontFamily());run.setFontSize(config.getFontSize());run.setText(text.substring(pageIdx + 6));}} else {run.setText(text);}}return this;}
}