Java 实现Html、富文本转word(docx格式)

该文章已生成可运行项目,

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;
  }
}

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值