liujie
2026-05-16 ecd1224448464d919839961dd52b9d2427610965
admin-server/src/main/java/com/estate/expert/admin/stat/report/render/WordNumberingSanitizer.java
@@ -2,12 +2,15 @@
import com.deepoove.poi.XWPFTemplate;
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.XWPFTableCell;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumLvl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumPr;
import java.math.BigInteger;
import java.util.List;
/**
 * 修复模板中缺失层级的编号段落,避免 POI 转换时读取 ilvl 空指针。
@@ -18,33 +21,118 @@
    }
    public static XWPFTemplate sanitize(XWPFTemplate template) {
        return sanitize(template, false);
    }
    public static XWPFTemplate sanitize(XWPFTemplate template, boolean normalizeDangerDetail) {
        if (template == null) {
            return null;
        }
        sanitizeDocument(template.getXWPFDocument());
        sanitizeDocument(template.getXWPFDocument(), normalizeDangerDetail);
        return template;
    }
    private static void sanitizeDocument(XWPFDocument document) {
    private static void sanitizeDocument(XWPFDocument document, boolean normalizeDangerDetail) {
        if (document == null) {
            return;
        }
        document.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph);
        if (normalizeDangerDetail) {
            normalizeDangerDetailHeadingNumbering(document, document.getParagraphs());
        }
        document.getTables().forEach(table -> table.getRows().forEach(row ->
                row.getTableCells().forEach(WordNumberingSanitizer::sanitizeCell)));
                row.getTableCells().forEach(cell -> sanitizeCell(document, cell, normalizeDangerDetail))));
        document.getHeaderList().forEach(header -> header.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
        document.getFooterList().forEach(footer -> footer.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
    }
    private static void sanitizeCell(XWPFTableCell cell) {
    private static void sanitizeCell(XWPFDocument document, XWPFTableCell cell, boolean normalizeDangerDetail) {
        if (cell == null) {
            return;
        }
        cell.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph);
        if (normalizeDangerDetail) {
            normalizeDangerDetailHeadingNumbering(document, cell.getParagraphs());
        }
        cell.getTables().forEach(table -> table.getRows().forEach(row ->
                row.getTableCells().forEach(WordNumberingSanitizer::sanitizeCell)));
                row.getTableCells().forEach(innerCell -> sanitizeCell(document, innerCell, normalizeDangerDetail))));
    }
    private static void normalizeDangerDetailHeadingNumbering(XWPFDocument document, List<XWPFParagraph> paragraphs) {
        boolean inDangerDetail = false;
        boolean pendingNewGroup = false;
        BigInteger currentProblemNumId = null;
        for (XWPFParagraph paragraph : paragraphs) {
            String styleId = styleId(paragraph);
            if (!inDangerDetail) {
                if (isDangerDetailTitle(styleId, paragraph.getText())) {
                    inDangerDetail = true;
                }
                continue;
            }
            if (isNextSectionTitle(styleId, paragraph.getText())) {
                return;
            }
            if ("4".equals(styleId) && hasVisibleText(paragraph.getText())) {
                pendingNewGroup = true;
                currentProblemNumId = null;
                continue;
            }
            if ("5".equals(styleId) && hasVisibleText(paragraph.getText())) {
                if (pendingNewGroup || currentProblemNumId == null) {
                    currentProblemNumId = createRestartNumber(document, paragraph);
                    pendingNewGroup = false;
                }
                paragraph.setNumID(currentProblemNumId);
                paragraph.setNumILvl(BigInteger.ZERO);
            }
        }
    }
    private static boolean isDangerDetailTitle(String styleId, String text) {
        return "2".equals(styleId) && text != null && text.contains("隐患") && text.contains("详单");
    }
    private static boolean isNextSectionTitle(String styleId, String text) {
        return ("1".equals(styleId) || "2".equals(styleId))
                && hasVisibleText(text)
                && !(text.contains("隐患") && text.contains("详单"));
    }
    private static boolean hasVisibleText(String text) {
        return text != null && !text.trim().isEmpty() && !text.trim().startsWith("{{");
    }
    private static String styleId(XWPFParagraph paragraph) {
        String styleId = paragraph.getStyleID();
        return styleId != null ? styleId : paragraph.getStyle();
    }
    private static BigInteger createRestartNumber(XWPFDocument document, XWPFParagraph paragraph) {
        XWPFNumbering numbering = document.getNumbering();
        if (numbering == null) {
            numbering = document.createNumbering();
        }
        BigInteger abstractNumId = paragraph.getNumID() == null
                ? BigInteger.ONE
                : numbering.getAbstractNumID(paragraph.getNumID());
        if (abstractNumId == null) {
            abstractNumId = BigInteger.ONE;
        }
        BigInteger numId = numbering.addNum(abstractNumId);
        CTNumLvl override = numbering.getNum(numId).getCTNum().addNewLvlOverride();
        override.setIlvl(BigInteger.ZERO);
        override.addNewStartOverride().setVal(BigInteger.ONE);
        return numId;
    }
    private static void sanitizeParagraph(XWPFParagraph paragraph) {