| | |
| | | |
| | | 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 空指针。 |
| | |
| | | } |
| | | |
| | | 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) { |