You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Apache POI获取Word文档样式及其属性以实现高质量DOCX转HTML

如何使用Apache POI获取Word文档样式及其属性以实现高质量DOCX转HTML

我完全懂你的困境——要在没有任何Office套件的生产服务器上,把DOCX转换成和Word原生导出一模一样的HTML,试了DocConv、docx4j等一堆工具效果都达不到要求,想用POI却卡在了获取完整样式属性这一步,尤其是那些定义在样式本身而非段落或Run对象里的格式(比如样式自带的加粗效果)。

其实用POI完全可以拿到Word文档里的所有样式及其完整属性,核心是要直接操作POI的样式表对象,还要处理样式的继承关系(因为Word里很多样式是基于基础样式扩展的,比如Normal样式)。下面我一步步给你讲怎么做,再附上修改后的代码示例:


第一步:获取文档的完整样式表

首先,从XWPFDocument中获取XWPFStyles对象,这是文档所有样式的容器,包括段落样式、字符样式、表格样式等。你可以通过xdoc.getStyles()直接拿到它。

第二步:遍历样式并提取属性

每个样式在POI里对应XWPFStyle对象,你需要区分段落样式(XWPFParagraphStyle)和字符样式(XWPFCharStyle),因为Word里这两种样式的属性逻辑是分开的。要获取样式的底层完整属性,得通过XWPFStyle.getCTStyle()拿到CTStyle对象,从这里可以读取所有定义的格式,包括:

  • 样式ID/名称
  • 段落对齐方式
  • 字体族、字号
  • 加粗、倾斜、下划线等字符格式
  • 段落缩进、行间距等段落格式

还要注意样式继承:很多样式会基于父样式(比如Normal),所以需要递归获取父样式的属性,合并到当前样式中,避免遗漏继承的格式。

第三步:把样式转换成CSS规则

在生成HTML的<style>标签里,把每个Word样式转换成对应的CSS类,这样后面段落/文本引用class时,就能自动应用样式里的所有格式,包括那些定义在样式而非段落/Run里的属性(比如样式自带的加粗)。


修改后的完整代码示例

下面是在你原有代码基础上修改后的版本,加入了获取样式并生成CSS的逻辑,解决了样式属性缺失的问题:

import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.model.XWPFStyles;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class Start2 {
    public static void main(String[] args) throws InvalidFormatException, IOException {
        String inputDoc = "D:/TestOutput/OriginalDoc.docx";
        String htmlStart = "<html>\r\n";
        String htmlHead = "<meta http-equiv='Content-Type' content='text/html; charset=windows-1252'>\r\n" +
                "<meta name='viewport' content='width=device-width, initial-scale=1.0'>\r\n";
        String htmlFilename = "D:/TestOutput/TestOutput.html";
        File htmlFile = new File(htmlFilename);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(htmlFile, false), StandardCharsets.UTF_8));

        FileInputStream fis = new FileInputStream(inputDoc);
        XWPFDocument xdoc = new XWPFDocument(OPCPackage.open(fis));
        List<XWPFParagraph> paragraphs = xdoc.getParagraphs();

        // -------------------------- 新增:获取并生成样式CSS --------------------------
        XWPFStyles styles = xdoc.getStyles();
        bw.write(htmlStart + "\r\n");
        bw.write("<head>\r\n");
        bw.write(htmlHead);
        bw.write("<style>\r\n");
        bw.write("html {font-family:Arial;}\r\n");

        // 遍历所有样式,生成对应CSS规则
        for (XWPFStyle style : styles.getStyles()) {
            String styleId = style.getStyleId();
            if (styleId == null || styleId.isEmpty()) continue;

            // 处理段落样式
            if (style instanceof XWPFParagraphStyle) {
                XWPFParagraphStyle paraStyle = (XWPFParagraphStyle) style;
                CTStyle ctStyle = paraStyle.getCTStyle();
                StringBuilder cssRule = new StringBuilder("p." + styleId + " {");

                // 提取段落对齐方式
                if (ctStyle.isSetPPr() && ctStyle.getPPr().isSetJc()) {
                    STJc.Enum align = ctStyle.getPPr().getJc().getVal();
                    String cssAlign = switch (align) {
                        case BOTH -> "justify";
                        case CENTER -> "center";
                        case RIGHT -> "right";
                        case LEFT -> "left";
                        default -> "left";
                    };
                    cssRule.append("text-align: ").append(cssAlign).append(";");
                }

                // 提取段落首行缩进
                if (ctStyle.isSetPPr() && ctStyle.getPPr().isSetInd()) {
                    CTInd ind = ctStyle.getPPr().getInd();
                    if (ind.isSetFirstLine()) {
                        // Word的缩进单位是twip,转成pt(1pt=20twip)
                        cssRule.append("text-indent: ").append(ind.getFirstLine().intValue() / 20).append("pt;");
                    }
                }

                // 提取字体相关属性
                if (ctStyle.isSetRPr()) {
                    CTRPr rPr = ctStyle.getRPr();
                    // 字体族
                    if (rPr.isSetRFonts()) {
                        CTFonts fonts = rPr.getRFonts();
                        if (fonts.isSetAscii()) {
                            cssRule.append("font-family: '").append(fonts.getAscii()).append("';");
                        }
                    }
                    // 字号(Word的sz是半磅单位,转成pt)
                    if (rPr.isSetSz()) {
                        cssRule.append("font-size: ").append(rPr.getSz().getVal().intValue() / 2).append("pt;");
                    }
                    // 加粗属性
                    if (rPr.isSetB() && rPr.getB().getVal()) {
                        cssRule.append("font-weight: bold;");
                    }
                }

                if (cssRule.length() > "p.".length() + styleId.length() + 2) {
                    cssRule.append("}\r\n");
                    bw.write(cssRule.toString());
                }
            }

            // 处理字符样式
            if (style instanceof XWPFCharStyle) {
                XWPFCharStyle charStyle = (XWPFCharStyle) style;
                CTStyle ctStyle = charStyle.getCTStyle();
                StringBuilder cssRule = new StringBuilder("span." + styleId + " {");

                if (ctStyle.isSetRPr()) {
                    CTRPr rPr = ctStyle.getRPr();
                    // 字体族
                    if (rPr.isSetRFonts()) {
                        CTFonts fonts = rPr.getRFonts();
                        if (fonts.isSetAscii()) {
                            cssRule.append("font-family: '").append(fonts.getAscii()).append("';");
                        }
                    }
                    // 字号
                    if (rPr.isSetSz()) {
                        cssRule.append("font-size: ").append(rPr.getSz().getVal().intValue() / 2).append("pt;");
                    }
                    // 加粗
                    if (rPr.isSetB() && rPr.getB().getVal()) {
                        cssRule.append("font-weight: bold;");
                    }
                    // 下划线
                    if (rPr.isSetU()) {
                        cssRule.append("text-decoration: underline;");
                    }
                }

                if (cssRule.length() > "span.".length() + styleId.length() + 2) {
                    cssRule.append("}\r\n");
                    bw.write(cssRule.toString());
                }
            }
        }

        bw.write("</style>\r\n");
        bw.write("</head>\r\n");
        bw.write("<body>\r\n");

        // -------------------------- 原有段落处理逻辑,优化样式应用 --------------------------
        for(XWPFParagraph para : paragraphs) {
            String paraStyleId = para.getStyle();
            if (paraStyleId != null && !paraStyleId.isEmpty()) {
                bw.write("<p class='" + paraStyleId + "'>");
            } else {
                bw.write("<p>");
            }

            // 处理段落的直接内联属性(优先级高于样式)
            StringBuilder inlineStyle = new StringBuilder();
            if (para.getFirstLineIndent() > 0) {
                inlineStyle.append("text-indent:").append(para.getFirstLineIndent()).append(";");
            }
            if(para.getAlignment() != null) {
                String align = para.getAlignment().name().toLowerCase();
                if (align.equals("both")) align = "justify";
                inlineStyle.append("text-align:").append(align).append(";");
            }
            if (!inlineStyle.isEmpty()) {
                bw.write(" style='" + inlineStyle + "'");
            }
            bw.write(">");

            // 处理Run,应用字符样式和直接格式
            for(XWPFRun run : para.getRuns()) {
                String runStyleId = run.getStyle();
                if (runStyleId != null && !runStyleId.isEmpty()) {
                    bw.write("<span class='" + runStyleId + "'>");
                }

                // 处理Run的直接内联格式
                StringBuilder runInlineStyle = new StringBuilder();
                if (run.isBold()) {
                    runInlineStyle.append("font-weight:bold;");
                }
                if (run.getFontFamily() != null) {
                    runInlineStyle.append("font-family:'").append(run.getFontFamily()).append("';");
                }
                if (!runInlineStyle.isEmpty()) {
                    bw.write("<span style='" + runInlineStyle + "'>");
                    bw.write(run.text());
                    bw.write("</span>");
                } else {
                    bw.write(run.text());
                }

                if (runStyleId != null && !runStyleId.isEmpty()) {
                    bw.write("</span>");
                }
            }

            bw.write("</p>\r\n");
        }

        bw.write("</body>\r\n");
        bw.write("</html>");
        bw.close();
        xdoc.close();
        fis.close();
        System.out.println("END PGM");
    }
}

关键说明

  1. 样式继承的补充处理:上面的代码基础版本只处理了当前样式的属性,如果需要处理样式继承(比如一个样式基于Normal),可以新增一个递归方法,从当前样式向上遍历父样式,合并所有属性。
  2. 单位转换:Word里的字号(sz)是半磅、缩进值是twip(1/20磅),代码里已经做了对应转换,确保CSS单位和Word格式一致。
  3. 格式优先级:内联样式(段落/Run的直接属性)会覆盖CSS类样式,这和Word的格式优先级逻辑完全一致(直接格式 > 样式)。

通过这种方式,你就能完整获取Word文档里的所有样式属性,生成对应的CSS,让HTML的格式和Word原生导出的几乎一致,解决你之前拿不到样式里的加粗等属性的问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:29:31