基于 Apache POI 实现的 Word 操作工具类
基于 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;}
}