pom
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
只支持行内样式,如果需要整体样式可使用jsoup赋值
word的标题、表格、图片、列表、字体都支持
源码:
package com.pgkt.ispin.erp.pjm.util;
import cn.hutool.core.util.StrUtil;
import com.pgkt.ispin.common.core.exception.BizException;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFAbstractNum;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFNumbering;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFStyle;
import org.apache.poi.xwpf.usermodel.XWPFStyles;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableCell.XWPFVertAlign;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STStyleType;
/**
* 基于POI将HTML或富文本内容转换为docx文档
*/
@Slf4j
public class HtmlToWordDocx {
/**
* 将HTML内容转换为Word文档内容
*
* @param document Word文档对象
* @param html HTML内容
*/
public static void htmlConvertWord(XWPFDocument document, String html) {
if (StrUtil.isEmpty(html)) {
return;
}
try {
Document parsedHtml = Jsoup.parse(html);
// 初始化html数据
initHtmlData(parsedHtml);
Elements bodyChildren = parsedHtml.body().children();
Map<String, Object> initStyle = new HashMap<>();
// 创建初始段落(防止空指针)
XWPFParagraph initPara = document.createParagraph();
if (!bodyChildren.isEmpty()) {
for (Node node : bodyChildren) {
processElement(node, initPara, initStyle, null, null);
}
}
} catch (Exception e) {
log.error("HTML转换Word失败", e);
}
}
/**
* 处理html数据。li标签内统一加上p标签(jsoup生成li,如果ul只有一个li没有p标签)
*/
private static void initHtmlData(Document parsedHtml) {
for (Element li : parsedHtml.select("li")) {
// 跳过已有<p>的列表项
if (!li.select("p").isEmpty()) {
continue;
}
// 获取所有子节点(包括文本和元素)
List<Node> originalNodes = new ArrayList<>();
for (Node node : li.childNodes()) {
// 必须克隆节点!
originalNodes.add(node.clone());
}
// 清空原内容
li.empty();
// 创建包裹的<p>标签
Element p = li.appendElement("p");
// 重新添加原有内容
for (Node node : originalNodes) {
p.appendChild(node);
}
}
}
/**
* 递归解析html的结构
*
* @param node 节点
* @param para 段落
* @param styleMap 样式集合
* @param cell table的单元格
* @param numId 列表的序号
*/
public static void processElement(Node node, XWPFParagraph para, Map<String, Object> styleMap,
XWPFTableCell cell, BigInteger numId) {
if (node instanceof TextNode) {
String text = ((TextNode) node).text().trim();
if (!text.isEmpty()) {
XWPFRun run = para.createRun();
run.setText(text);
applyStyle(run, styleMap);
}
} else if (node instanceof Element) {
Element el = (Element) node;
String tag = el.tagName().toLowerCase();
switch (tag) {
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
processHeading(para.getDocument(), el, styleMap);
break;
case "p":
processParagraph(para.getDocument(), el, styleMap, cell, numId);
break;
case "span":
case "strong":
case "i":
case "b":
case "u":
case "s":
case "em":
processLabStyle(para, styleMap, el);
break;
case "ul":
case "ol":
processList(para.getDocument(), el, styleMap);
break;
case "li":
processLi(para, styleMap, numId, el);
break;
case "table":
processTable(para.getDocument(), el, styleMap);
break;
case "img":
processImage(para.getDocument(), el);
break;
case "hr":
createHorizontalRule(para.getDocument());
break;
case "br":
createLineBreak(para.getDocument(), cell);
break;
// 其他标签可扩展
default:
for (Node child : el.childNodes()) {
processElement(child, para, styleMap, null, numId);
}
break;
}
}
}
/**
* 解析li标签
*/
private static void processLi(XWPFParagraph para, Map<String, Object> styleMap,
BigInteger numId, Element el) {
List<Element> children = el.children();
// li 下面只有p标签,可能会多个p
for (int i = 0; i < children.size(); i++) {
if (i != 0) {
// 第一个li下面的p对应到word的li,递归到p标签处理
// 标记值 -1
numId = BigInteger.valueOf(-1);
}
processElement(children.get(i), para, styleMap, null, numId);
}
}
/**
* 样式标签
*/
private static void processLabStyle(XWPFParagraph para, Map<String, Object> styleMap, Element el) {
roundProcessElement(el, styleMap, para);
}
public static void applyStyle(XWPFRun run, Map<String, Object> styleMap) {
if (styleMap == null || styleMap.isEmpty()) {
return;
}
if ("bold".equals(styleMap.get("font-weight")) || Boolean.TRUE.equals(styleMap.get("bold"))) {
run.setBold(true);
}
if ("italic".equals(styleMap.get("font-style")) || Boolean.TRUE.equals(styleMap.get("italic"))) {
run.setItalic(true);
}
if (styleMap.containsKey("color")) {
String color = ((String) styleMap.get("color")).replace("#", "");
// 将颜色名称转换为十六进制值
String hexColor = convertColorNameToHex(color);
if (hexColor != null && !hexColor.isEmpty()) {
run.setColor(hexColor);
}
}
if (styleMap.containsKey("font-size")) {
run.setFontSize(parseFontSize((String) styleMap.get("font-size")));
}
if (styleMap.containsKey("text-decoration-u")) {
// 下划线
run.setUnderline(UnderlinePatterns.SINGLE);
}
if (styleMap.containsKey("text-decoration-s")) {
// 删除线
run.setStrikeThrough(true);
}
}
/**
* 处理图片
*/
private static void processImage(XWPFDocument document, Element element) {
XWPFParagraph paragraph = document.createParagraph();
String src = element.attr("src");
if (src.startsWith("data:image")) {
String[] parts = src.split(";base64,");
if (parts.length == 2) {
// 获取文件后缀
String mimeType = parts[0];
String suffix = mimeType.split("/")[1];
// 解码 base64 字符串
byte[] imageBytes = Base64.getDecoder().decode(parts[1]);
// 添加图片到文档
try {
XWPFRun run = paragraph.createRun();
// 添加图片到段落中,根据实际情况调整图片格式
int format = XWPFDocument.PICTURE_TYPE_JPEG;
// 图片文件名
String fileName = "image_" + System.currentTimeMillis() + "." + suffix;
// 获取图片的宽度和高度属性,限制最大600x400
Dimension dimensions = getAdjustedImageDimensions(imageBytes, 600, 400);
int width = dimensions.width;
int height = dimensions.height;
// 调整图片大小
run.addPicture(new ByteArrayInputStream(imageBytes), format, fileName, Units.toEMU(width), Units.toEMU(height));
} catch (Exception e) {
log.error("添加图片到Word文档失败", e);
throw new BizException("添加图片到Word文档失败");
}
}
}
}
/**
* 获取图片尺寸并自动按最大宽高限制等比缩放
*
* @param imageBytes 图片字节数组
* @param maxWidth 最大允许宽度(像素)
* @param maxHeight 最大允许高度(像素)
* @return 缩放后的尺寸对象(如果不需要缩放则返回原尺寸)
* @throws Exception 图片解析失败抛出异常
*/
public static Dimension getAdjustedImageDimensions(byte[] imageBytes, int maxWidth, int maxHeight) throws Exception {
try (ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes)) {
BufferedImage image = ImageIO.read(bis);
if (image == null) {
throw new IllegalArgumentException("无法解析图片数据");
}
int originalWidth = image.getWidth();
int originalHeight = image.getHeight();
// 如果原始尺寸已经在限制范围内,则无需缩放
if (originalWidth <= maxWidth && originalHeight <= maxHeight) {
return new Dimension(originalWidth, originalHeight);
}
// 计算等比例缩放比例
double widthRatio = (double) maxWidth / originalWidth;
double heightRatio = (double) maxHeight / originalHeight;
double ratio = Math.min(widthRatio, heightRatio);
// 计算缩放后的尺寸
int scaledWidth = (int) (originalWidth * ratio);
int scaledHeight = (int) (originalHeight * ratio);
return new Dimension(scaledWidth, scaledHeight);
}
}
/**
* 处理标题
*/
private static void processHeading(XWPFDocument document, Element headingElement, Map<String, Object> styleMap) {
String tagName = headingElement.tagName().toLowerCase();
int headingLevel = Integer.parseInt(tagName.substring(1));
XWPFParagraph paragraph = document.createParagraph();
paragraph.setStyle("Heading" + headingLevel);
addCustomHeadingStyle(document, "Heading" + headingLevel, headingLevel);
roundProcessElement(headingElement, styleMap, paragraph);
}
/**
* 处理段落
*/
private static void processParagraph(XWPFDocument document, Element paragraphElement,
Map<String, Object> styleMap, XWPFTableCell cell, BigInteger numId) {
XWPFParagraph paragraph;
if (null != cell) {
//单元格cell中,td有p标签换行的情况
paragraph = cell.getParagraphs().isEmpty() ?
cell.addParagraph() : cell.getParagraphs().get(0);
} else {
paragraph = document.createParagraph();
if (null != numId) {
// li下面的p标签
if (numId.intValue() == -1) {
// 设置缩进
paragraph.setIndentationLeft(450);
} else {
// 设置列表样式
paragraph.setNumID(numId);
}
}
}
roundProcessElement(paragraphElement, styleMap, paragraph);
}
/**
* 递归子节点
*/
private static void roundProcessElement(Element paragraphElement, Map<String, Object> styleMap, XWPFParagraph paragraph) {
Map<String, Object> merged = mergeStyle(styleMap, parseStyle(paragraphElement));
for (Node child : paragraphElement.childNodes()) {
processElement(child, paragraph, merged, null, null);
}
}
/**
* 递归子节点
*/
private static void roundProcessElementForTable(Element paragraphElement, Map<String, Object> styleMap, XWPFParagraph paragraph,
XWPFTableCell cell) {
Map<String, Object> merged = mergeStyle(styleMap, parseStyle(paragraphElement));
for (Node child : paragraphElement.childNodes()) {
processElement(child, paragraph, merged, cell, null);
}
}
/**
* 处理列表
*/
private static void processList(XWPFDocument document, Element listElement, Map<String, Object> styleMap) {
boolean isOrdered = "ol".equalsIgnoreCase(listElement.tagName());
// 动态生成唯一的 numID
BigInteger numId = addListStyles(document, isOrdered);
for (Element liElementChild : listElement.children()) {
if ("li".equalsIgnoreCase(liElementChild.tagName())) {
// 处理内容
processElement(liElementChild, document.getLastParagraph(), styleMap, null, numId);
}
}
}
/**
* 设置列表的样式
*/
private static BigInteger addListStyles(XWPFDocument document, boolean isOrdered) {
// 初始化文档的编号定义部分
XWPFNumbering numbering = document.getNumbering();
if (numbering == null) {
numbering = document.createNumbering();
}
// 查找下一个可用的 abstractNumId
BigInteger abstractNumId = BigInteger.valueOf(1);
while (numbering.getAbstractNum(abstractNumId) != null) {
abstractNumId = abstractNumId.add(BigInteger.ONE);
}
// 创建有序或无序列表样式
CTAbstractNum ctAbstractNum = CTAbstractNum.Factory.newInstance();
ctAbstractNum.setAbstractNumId(abstractNumId);
CTLvl ctLvl = ctAbstractNum.addNewLvl();
ctLvl.setIlvl(BigInteger.ZERO);
if (isOrdered) {
// 有序列表样式
ctLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL);
ctLvl.addNewLvlText().setVal("%1.");
ctLvl.addNewStart().setVal(BigInteger.ONE);
} else {
// 无序列表样式
ctLvl.addNewNumFmt().setVal(STNumberFormat.BULLET);
ctLvl.addNewLvlText().setVal("•");
}
// 将样式添加到文档
XWPFAbstractNum abstractNum = new XWPFAbstractNum(ctAbstractNum);
numbering.addAbstractNum(abstractNum);
return numbering.addNum(abstractNumId);
}
/**
* 处理表格
*
* @param document Word文档对象
* @param tableElement HTML表格元素
*/
private static void processTable(XWPFDocument document, Element tableElement, Map<String, Object> styleMap) {
// 创建Word表格
XWPFTable table = document.createTable();
// 获取HTML表格的所有行
Elements rows = tableElement.select("tr");
// 存储合并信息(rowspan/colspan处理)
int[][] mergeInfo = new int[rows.size()][];
int maxCols = 0;
// 第一遍:计算最大列数和初始化合并信息
for (int i = 0; i < rows.size(); i++) {
Elements cells = rows.get(i).select("td, th");
mergeInfo[i] = new int[cells.size()];
maxCols = Math.max(maxCols, cells.size());
}
// 第二遍:处理实际单元格内容
for (int rowIdx = 0; rowIdx < rows.size(); rowIdx++) {
Element row = rows.get(rowIdx);
Elements cells = row.select("td, th");
// 获取或创建Word表格行
XWPFTableRow tableRow = rowIdx < table.getNumberOfRows() ?
table.getRow(rowIdx) : table.createRow();
// 设置行高,增加行之间的间距
tableRow.setHeight(400);
int colIdx = 0;
for (Element cell : cells) {
// 处理rowspan和colspan
int rowspan = Math.max(1, cell.hasAttr("rowspan") ?
Integer.parseInt(cell.attr("rowspan")) : 1);
int colspan = Math.max(1, cell.hasAttr("colspan") ?
Integer.parseInt(cell.attr("colspan")) : 1);
// 跳过被合并的单元格位置
while (colIdx < mergeInfo[rowIdx].length && mergeInfo[rowIdx][colIdx] == 1) {
colIdx++;
}
// 获取或创建单元格
XWPFTableCell tableCell = colIdx < tableRow.getTableCells().size() ?
tableRow.getCell(colIdx) : tableRow.addNewTableCell();
// 设置单元格内容
processCellContent(tableCell, cell, styleMap);
// 处理合并
if (rowspan > 1 || colspan > 1) {
mergeCells(table, rowIdx, colIdx, rowspan, colspan);
}
// 标记合并区域
for (int r = 0; r < rowspan; r++) {
if (rowIdx + r >= mergeInfo.length) {
break;
}
for (int c = 0; c < colspan; c++) {
if (colIdx + c >= mergeInfo[rowIdx + r].length) {
break;
}
if (r == 0 && c == 0) {
continue; // 主单元格不标记
}
// 标记为被合并
mergeInfo[rowIdx + r][colIdx + c] = 1;
}
}
colIdx += colspan;
}
}
// 设置表格样式(可选)
setTableStyle(table);
}
/**
* 处理单元格内容
*/
private static void processCellContent(XWPFTableCell cell, Element htmlCell, Map<String, Object> styleMap) {
// 清除默认段落(如果存在)
if (!cell.getParagraphs().isEmpty()) {
cell.removeParagraph(0);
}
// 创建新段落并设置内容
XWPFParagraph para = cell.addParagraph();
Map<String, Object> nextStyle = new HashMap<>(styleMap);
// 可以添加更多样式处理
if ("th".equals(htmlCell.tagName())) {
nextStyle.put("bold", true);
}
// 垂直居中对齐
cell.setVerticalAlignment(XWPFVertAlign.CENTER);
// 递归
roundProcessElementForTable(htmlCell, nextStyle, para, cell);
}
/**
* 合并单元格(修复版,支持复杂合并)
*
* @param table 表格对象
* @param row 起始行(0-based)
* @param col 起始列(0-based)
* @param rowspan 向下合并行数(≥1)
* @param colspan 向右合并列数(≥1)
*/
private static void mergeCells(XWPFTable table, int row, int col, int rowspan, int colspan) {
// 1. 参数校验
if (row < 0 || col < 0 || rowspan < 1 || colspan < 1) {
throw new IllegalArgumentException("Invalid merge parameters: row=" + row + ", col=" + col
+ ", rowspan=" + rowspan + ", colspan=" + colspan);
}
// 2. 动态扩展表格(修复可能的多行多列不足问题)
for (int i = row; i < row + rowspan; i++) {
// 处理行不足
while (i >= table.getNumberOfRows()) {
XWPFTableRow newRow = table.createRow();
// 保持与首列相同的单元格数(避免列数不一致)
if (table.getNumberOfRows() > 1) {
int firstRowCells = table.getRow(0).getTableCells().size();
while (newRow.getTableCells().size() < firstRowCells) {
newRow.createCell();
}
}
}
// 处理列不足
XWPFTableRow currentRow = table.getRow(i);
for (int j = col; j < col + colspan; j++) {
while (j >= currentRow.getTableCells().size()) {
currentRow.createCell();
}
}
}
// 3. 获取主单元格属性(确保存在)
XWPFTableCell mainCell = table.getRow(row).getCell(col);
CTTcPr mainTcPr = mainCell.getCTTc().isSetTcPr() ?
mainCell.getCTTc().getTcPr() : mainCell.getCTTc().addNewTcPr();
// 4. 仅当需要合并时操作
if (rowspan > 1 || colspan > 1) {
// 4.1 设置主单元格合并标记(修复重复添加属性问题)
if (rowspan > 1) {
if (mainTcPr.isSetVMerge()) {
mainTcPr.getVMerge().setVal(STMerge.RESTART);
} else {
mainTcPr.addNewVMerge().setVal(STMerge.RESTART);
}
}
if (colspan > 1) {
if (mainTcPr.isSetHMerge()) {
mainTcPr.getHMerge().setVal(STMerge.RESTART);
} else {
mainTcPr.addNewHMerge().setVal(STMerge.RESTART);
}
}
// 4.2 处理被合并的单元格(优化内容清除方式)
for (int i = row; i < row + rowspan; i++) {
for (int j = col; j < col + colspan; j++) {
if (i == row && j == col) {
continue; // 主单元格不需要处理
}
XWPFTableCell cell = table.getRow(i).getCell(j);
// 高效清除内容(直接操作底层XML)
for (int k = cell.getParagraphs().size() - 1; k >= 0; k--) {
cell.removeParagraph(k);
}
// 获取或创建属性
CTTcPr tcPr = cell.getCTTc().isSetTcPr() ?
cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();
// 清除独立边框(避免渲染异常)
if (tcPr.isSetTcBorders()) {
tcPr.unsetTcBorders();
}
// 设置合并属性
if (rowspan > 1) {
if (tcPr.isSetVMerge()) {
tcPr.getVMerge().setVal(STMerge.CONTINUE);
} else {
tcPr.addNewVMerge().setVal(STMerge.CONTINUE);
}
}
if (colspan > 1) {
if (tcPr.isSetHMerge()) {
tcPr.getHMerge().setVal(STMerge.CONTINUE);
} else {
tcPr.addNewHMerge().setVal(STMerge.CONTINUE);
}
}
}
}
// 4.3 确保主单元格有可见边框
if (!mainTcPr.isSetTcBorders()) {
CTTcBorders borders = mainTcPr.addNewTcBorders();
borders.addNewLeft().setVal(STBorder.SINGLE);
borders.addNewRight().setVal(STBorder.SINGLE);
borders.addNewTop().setVal(STBorder.SINGLE);
borders.addNewBottom().setVal(STBorder.SINGLE);
}
}
}
/**
* 设置表格样式,包括单元格背景颜色、字体颜色、文本对齐方式等
*
* @param table Word表格对象
*/
private static void setTableStyle(XWPFTable table) {
// 1. 设置表格宽度为100%
table.setWidth("100%");
// 2. 设置表格边框(1px黑色实线,合并边框)
setTableBorders(table);
// 3. 设置表格单元格边距
table.setCellMargins(60, 60, 60, 60);
// 4. 设置表格整体属性
CTJc jc = table.getCTTbl().getTblPr().getJc();
if (jc == null) {
jc = table.getCTTbl().getTblPr().addNewJc();
}
//设置表格内容居中
jc.setVal(STJc.CENTER);
table.getCTTbl().getTblPr().setJc(jc);
}
/**
* 设置表格边框
*/
private static void setTableBorders(XWPFTable table) {
CTTblPr tblPr = table.getCTTbl().getTblPr();
if (tblPr == null) {
tblPr = table.getCTTbl().addNewTblPr();
}
CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders();
// 设置所有边框(1px黑色实线)
setBorder(borders.addNewTop());
setBorder(borders.addNewLeft());
setBorder(borders.addNewBottom());
setBorder(borders.addNewRight());
setBorder(borders.addNewInsideH());
setBorder(borders.addNewInsideV());
}
/**
* 辅助方法:设置边框
*/
private static void setBorder(CTBorder border) {
border.setVal(STBorder.SINGLE);
border.setColor("000000");
border.setSz(BigInteger.valueOf(4));
border.setSpace(BigInteger.ZERO);
}
/**
* 解析字体大小
*/
private static int parseFontSize(String value) {
// 如果值是像素值,例如 "10px"
if (value.endsWith("px")) {
//px转换为pt
int pxValue = Integer.parseInt(value.replace("px", "").trim());
return (int) (pxValue * 0.75);
} else if (value.endsWith("pt")) {
return Integer.parseInt(value.replace("pt", "").trim());
}
// 如果是其他类型的 font-size,可以在这里处理(例如 em, rem 等)
return 10;
}
/**
* 创建水平线
*/
private static void createHorizontalRule(XWPFDocument document) {
XWPFParagraph hrParagraph = document.createParagraph();
XWPFRun run = hrParagraph.createRun();
run.addBreak();
run.addBreak(BreakType.TEXT_WRAPPING);
}
/**
* 创建换行
*/
private static void createLineBreak(XWPFDocument document, XWPFTableCell cell) {
XWPFParagraph paragraph;
if (null != cell) {
// 单元格内换行方案
paragraph = cell.getParagraphs().isEmpty() ?
cell.addParagraph() : cell.getParagraphs().get(0);
XWPFRun run = paragraph.createRun();
run.addBreak(BreakType.TEXT_WRAPPING);
run.setText("");
} else {
paragraph = document.createParagraph();
paragraph.createRun().addBreak();
}
}
/**
* 增加自定义标题样式
*
* @param docxDocument 目标文档
* @param strStyleId 样式名称
* @param headingLevel 样式级别
*/
private static void addCustomHeadingStyle(XWPFDocument docxDocument, String strStyleId, int headingLevel) {
// 1. 基础样式定义
CTStyle ctStyle = CTStyle.Factory.newInstance();
ctStyle.setStyleId(strStyleId);
// 样式名称
CTString styleName = CTString.Factory.newInstance();
styleName.setVal(strStyleId);
ctStyle.setName(styleName);
// 大纲级别
CTDecimalNumber indentNumber = CTDecimalNumber.Factory.newInstance();
indentNumber.setVal(BigInteger.valueOf(headingLevel));
// 2. 设置样式显示属性
ctStyle.setUiPriority(indentNumber);
ctStyle.setUnhideWhenUsed(CTOnOff.Factory.newInstance());
ctStyle.setQFormat(CTOnOff.Factory.newInstance());
// 3. 段落属性(大纲级别)
CTPPr ppr = CTPPr.Factory.newInstance();
ppr.setOutlineLvl(indentNumber);
ctStyle.setPPr(ppr);
// 4. 字体属性(关键新增部分)
CTRPr rpr = CTRPr.Factory.newInstance();
// 字体大小计算(h1=22pt, h2=20pt... h6=12pt)
int fontSizePt = 22 - (Math.min(headingLevel, 6) - 1) * 2;
rpr.addNewSz().setVal(BigInteger.valueOf(fontSizePt * 2L));
rpr.addNewSzCs().setVal(BigInteger.valueOf(fontSizePt * 2L));
// 默认加粗
rpr.addNewB().setVal(STOnOff.TRUE);
ctStyle.setRPr(rpr);
// 5. 添加到文档样式库
XWPFStyle style = new XWPFStyle(ctStyle);
style.setType(STStyleType.PARAGRAPH);
XWPFStyles styles = docxDocument.createStyles();
styles.addStyle(style);
}
/**
* 将颜色名称转换为十六进制值
*/
private static String convertColorNameToHex(String colorName) {
switch (colorName.toLowerCase()) {
case "red":
return "FF0000";
case "blue":
return "0000FF";
case "green":
return "00FF00";
case "black":
return "000000";
case "white":
return "FFFFFF";
case "yellow":
return "FFFF00";
case "gray":
case "grey":
return "808080";
default:
// 如果无法识别颜色名称,返回 原代码
return colorName;
}
}
/**
* 样式集合
*/
public static Map<String, Object> mergeStyle(Map<String, Object> parent, Map<String, Object> current) {
Map<String, Object> merged = new HashMap<>();
if (parent != null) {
merged.putAll(parent);
}
if (current != null) {
merged.putAll(current);
}
return merged;
}
/**
* 解析样式
*/
public static Map<String, Object> parseStyle(Element el) {
Map<String, Object> map = new HashMap<>();
String style = el.attr("style");
if (StrUtil.isNotBlank(style)) {
for (String s : style.split(";")) {
String[] kv = s.split(":");
if (kv.length == 2) {
map.put(kv[0].trim().toLowerCase(), kv[1].trim());
}
}
}
String tag = el.tagName().toLowerCase();
switch (tag) {
case "strong":
case "b":
map.put("font-weight", "bold");
break;
case "i":
case "em":
map.put("font-style", "italic");
break;
case "u":
map.put("text-decoration-u", "underline");
break;
case "s":
map.put("text-decoration-s", "line-through");
break;
default:
break;
}
return map;
}
}
&spm=1001.2101.3001.5002&articleId=147153295&d=1&t=3&u=b2da2820b35c4e1dad1eb3d2b98c36f7)
2867

被折叠的 条评论
为什么被折叠?



