package com.estate.expert.admin.stat.report.render;
|
|
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.XWPFRun;
|
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
|
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
|
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 空指针。
|
*/
|
public final class WordNumberingSanitizer {
|
|
private WordNumberingSanitizer() {
|
}
|
|
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(), normalizeDangerDetail);
|
return template;
|
}
|
|
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(cell -> sanitizeCell(document, cell, normalizeDangerDetail))));
|
if (normalizeDangerDetail) {
|
boldSummaryRows(document, "综合得分");
|
}
|
document.getHeaderList().forEach(header -> header.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
|
document.getFooterList().forEach(footer -> footer.getParagraphs().forEach(WordNumberingSanitizer::sanitizeParagraph));
|
}
|
|
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(innerCell -> sanitizeCell(document, innerCell, normalizeDangerDetail))));
|
}
|
|
private static void boldSummaryRows(XWPFDocument document, String markerText) {
|
if (document == null || markerText == null || markerText.isBlank()) {
|
return;
|
}
|
|
for (XWPFTable table : document.getTables()) {
|
for (XWPFTableRow row : table.getRows()) {
|
boolean summaryRow = row.getTableCells().stream()
|
.map(XWPFTableCell::getText)
|
.anyMatch(text -> text != null && text.contains(markerText));
|
if (!summaryRow) {
|
continue;
|
}
|
for (XWPFTableCell cell : row.getTableCells()) {
|
for (XWPFParagraph paragraph : cell.getParagraphs()) {
|
for (XWPFRun run : paragraph.getRuns()) {
|
run.setBold(true);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
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) {
|
if (paragraph == null || !paragraph.getCTP().isSetPPr()) {
|
return;
|
}
|
|
var paragraphProperties = paragraph.getCTP().getPPr();
|
if (!paragraphProperties.isSetNumPr()) {
|
return;
|
}
|
|
CTNumPr numPr = paragraphProperties.getNumPr();
|
if (numPr.getNumId() != null && numPr.getIlvl() == null) {
|
CTDecimalNumber ilvl = numPr.addNewIlvl();
|
ilvl.setVal(BigInteger.ZERO);
|
}
|
}
|
}
|