|
|
@@ -0,0 +1,447 @@
|
|
|
+package com.hotent.util.wordexcelutils;/**
|
|
|
+ * @program: cbjs-mvue-master
|
|
|
+ * @description:
|
|
|
+ * @author: zhao yue yue
|
|
|
+ * @create: 2025-12-03 11:13
|
|
|
+ */
|
|
|
+
|
|
|
+import org.apache.poi.xwpf.usermodel.*;
|
|
|
+
|
|
|
+import java.math.BigInteger;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ *@author: zhao yue yue
|
|
|
+ *@create: 2025-12-03 11:13
|
|
|
+ */
|
|
|
+public class RobustComplexDocumentProcessor {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理复杂文档:使用Map和List作为数据结构
|
|
|
+ */
|
|
|
+ public static void processDocumentWithMaps(XWPFDocument document,
|
|
|
+ Map<String, String> textReplacements,
|
|
|
+ List<String> years,
|
|
|
+ List<Map<String, Object>> costItems) {
|
|
|
+
|
|
|
+ System.out.println("开始处理文档(Map版)...");
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 替换所有文本占位符
|
|
|
+ replaceAllTextPlaceholders(document, textReplacements);
|
|
|
+
|
|
|
+ // 2. 处理定价成本核定表
|
|
|
+ processCostTableWithMaps(document, years, costItems);
|
|
|
+
|
|
|
+ System.out.println("文档处理完成!");
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("处理过程中出现错误: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 替换所有文本占位符
|
|
|
+ */
|
|
|
+ private static void replaceAllTextPlaceholders(XWPFDocument document,
|
|
|
+ Map<String, String> replacements) {
|
|
|
+ System.out.println("开始替换文本占位符...");
|
|
|
+
|
|
|
+ // 存储替换统计
|
|
|
+ Map<String, Boolean> replacementStats = new HashMap<>();
|
|
|
+
|
|
|
+ // 尝试多种替换策略
|
|
|
+ for (Map.Entry<String, String> entry : replacements.entrySet()) {
|
|
|
+ String placeholder = entry.getKey();
|
|
|
+ String value = entry.getValue();
|
|
|
+
|
|
|
+ boolean success = false;
|
|
|
+
|
|
|
+ // 策略1:在段落中直接替换
|
|
|
+ success = replaceInParagraphs(document, placeholder, value);
|
|
|
+
|
|
|
+ // 策略2:在表格中替换
|
|
|
+ if (!success) {
|
|
|
+ success = replaceInTables(document, placeholder, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 策略3:使用智能替换
|
|
|
+ if (!success) {
|
|
|
+ success = smartReplaceInDocument(document, placeholder, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ replacementStats.put(placeholder, success);
|
|
|
+
|
|
|
+ String status = success ? "✅" : "❌";
|
|
|
+ System.out.println(status + " " + placeholder + " → " + value +
|
|
|
+ (success ? "" : " (未找到)"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打印替换统计
|
|
|
+ long successCount = replacementStats.values().stream().filter(v -> v).count();
|
|
|
+ long totalCount = replacementStats.size();
|
|
|
+ System.out.println("替换完成: " + successCount + "/" + totalCount + " 个占位符");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在段落中替换
|
|
|
+ */
|
|
|
+ private static boolean replaceInParagraphs(XWPFDocument doc, String placeholder, String value) {
|
|
|
+ boolean found = false;
|
|
|
+ for (XWPFParagraph paragraph : doc.getParagraphs()) {
|
|
|
+ if (replaceInParagraph(paragraph, placeholder, value)) {
|
|
|
+ found = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return found;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在表格中替换
|
|
|
+ */
|
|
|
+ private static boolean replaceInTables(XWPFDocument doc, String placeholder, String value) {
|
|
|
+ boolean found = false;
|
|
|
+ for (XWPFTable table : doc.getTables()) {
|
|
|
+ for (XWPFTableRow row : table.getRows()) {
|
|
|
+ for (XWPFTableCell cell : row.getTableCells()) {
|
|
|
+ for (XWPFParagraph paragraph : cell.getParagraphs()) {
|
|
|
+ if (replaceInParagraph(paragraph, placeholder, value)) {
|
|
|
+ found = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return found;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 智能替换
|
|
|
+ */
|
|
|
+ private static boolean smartReplaceInDocument(XWPFDocument doc, String placeholder, String value) {
|
|
|
+ // 尝试处理带格式的占位符
|
|
|
+ String cleanPlaceholder = placeholder
|
|
|
+ .replaceAll("[\\[\\]{}]", "")
|
|
|
+ .replaceAll("\\s+", "");
|
|
|
+
|
|
|
+ if (cleanPlaceholder.length() > 3) {
|
|
|
+ // 在文档中搜索类似的文本
|
|
|
+ return searchAndReplaceFuzzy(doc, cleanPlaceholder, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 在段落中执行替换
|
|
|
+ */
|
|
|
+ private static boolean replaceInParagraph(XWPFParagraph paragraph, String findText, String replaceText) {
|
|
|
+ boolean replaced = false;
|
|
|
+ for (XWPFRun run : paragraph.getRuns()) {
|
|
|
+ String text = run.getText(0);
|
|
|
+ if (text != null && text.contains(findText)) {
|
|
|
+ run.setText(text.replace(findText, replaceText), 0);
|
|
|
+ replaced = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return replaced;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 模糊搜索和替换
|
|
|
+ */
|
|
|
+ private static boolean searchAndReplaceFuzzy(XWPFDocument doc, String search, String replace) {
|
|
|
+ boolean found = false;
|
|
|
+
|
|
|
+ // 搜索段落
|
|
|
+ for (XWPFParagraph paragraph : doc.getParagraphs()) {
|
|
|
+ String text = paragraph.getText();
|
|
|
+ if (text != null && text.toLowerCase().contains(search.toLowerCase())) {
|
|
|
+ // 在runs中查找具体位置
|
|
|
+ for (XWPFRun run : paragraph.getRuns()) {
|
|
|
+ String runText = run.getText(0);
|
|
|
+ if (runText != null && runText.toLowerCase().contains(search.toLowerCase())) {
|
|
|
+ run.setText(runText.replaceAll("(?i)" + search, replace), 0);
|
|
|
+ found = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return found;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用Map处理定价成本核定表
|
|
|
+ */
|
|
|
+ private static void processCostTableWithMaps(XWPFDocument document,
|
|
|
+ List<String> years,
|
|
|
+ List<Map<String, Object>> costItems) {
|
|
|
+ System.out.println("开始处理定价成本核定表...");
|
|
|
+
|
|
|
+ // 找到所有表格
|
|
|
+ List<XWPFTable> tables = document.getTables();
|
|
|
+ if (tables.isEmpty()) {
|
|
|
+ System.out.println("未找到表格,创建新表格");
|
|
|
+ createNewCostTable(document, years, costItems);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 假设最后一个表格是定价成本核定表
|
|
|
+ XWPFTable costTable = tables.get(tables.size() - 1);
|
|
|
+ System.out.println("找到表格,当前行数: " + costTable.getRows().size());
|
|
|
+
|
|
|
+ // 重建表格
|
|
|
+ rebuildCostTableWithMaps(costTable, years, costItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用Map重建成本核定表
|
|
|
+ */
|
|
|
+ private static void rebuildCostTableWithMaps(XWPFTable table,
|
|
|
+ List<String> years,
|
|
|
+ List<Map<String, Object>> costItems) {
|
|
|
+ try {
|
|
|
+ // 1. 找到表头行
|
|
|
+ int headerRowIndex = findCostTableHeaderRow(table);
|
|
|
+ System.out.println("表头行索引: " + headerRowIndex);
|
|
|
+
|
|
|
+ // 2. 清空数据区域
|
|
|
+ clearCostTableData(table, headerRowIndex);
|
|
|
+
|
|
|
+ // 3. 重建表头(如果需要)
|
|
|
+ rebuildTableHeader(table, years, headerRowIndex);
|
|
|
+
|
|
|
+ // 4. 添加数据行
|
|
|
+ addCostTableDataRows(table, years, costItems, headerRowIndex);
|
|
|
+
|
|
|
+ // 5. 添加说明行
|
|
|
+ addCostTableFooter(table);
|
|
|
+
|
|
|
+ System.out.println("表格处理完成,最终行数: " + table.getRows().size());
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("表格处理失败: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 找到成本表表头行
|
|
|
+ */
|
|
|
+ private static int findCostTableHeaderRow(XWPFTable table) {
|
|
|
+ for (int i = 0; i < table.getRows().size(); i++) {
|
|
|
+ XWPFTableRow row = table.getRows().get(i);
|
|
|
+ String rowText = getRowText(row);
|
|
|
+ if (rowText != null &&
|
|
|
+ (rowText.contains("成本项目") || rowText.contains("定价成本"))) {
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0; // 默认第一行
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空表格数据区域
|
|
|
+ */
|
|
|
+ private static void clearCostTableData(XWPFTable table, int headerRowIndex) {
|
|
|
+ // 保留表头行,移除后面的行
|
|
|
+ for (int i = table.getRows().size() - 1; i > headerRowIndex; i--) {
|
|
|
+ table.removeRow(i);
|
|
|
+ }
|
|
|
+ System.out.println("清理数据区域完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重建表头
|
|
|
+ */
|
|
|
+ private static void rebuildTableHeader(XWPFTable table,
|
|
|
+ List<String> years,
|
|
|
+ int headerRowIndex) {
|
|
|
+ XWPFTableRow headerRow;
|
|
|
+
|
|
|
+ if (headerRowIndex < table.getRows().size()) {
|
|
|
+ headerRow = table.getRows().get(headerRowIndex);
|
|
|
+ } else {
|
|
|
+ headerRow = table.createRow();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空现有单元格
|
|
|
+ for (XWPFTableCell cell : headerRow.getTableCells()) {
|
|
|
+ clearCellContent(cell);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保有足够的单元格
|
|
|
+ int neededCells = 2 + years.size(); // 成本项目 + 行次 + 各年份
|
|
|
+ while (headerRow.getTableCells().size() < neededCells) {
|
|
|
+ headerRow.addNewTableCell();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置表头内容
|
|
|
+ List<XWPFTableCell> cells = headerRow.getTableCells();
|
|
|
+
|
|
|
+ // 成本项目列
|
|
|
+ setCellText(cells.get(0), "成本项目");
|
|
|
+
|
|
|
+ // 行次列
|
|
|
+ setCellText(cells.get(1), "行次");
|
|
|
+
|
|
|
+ // 各年份列
|
|
|
+ for (int i = 0; i < years.size(); i++) {
|
|
|
+ setCellText(cells.get(2 + i), years.get(i) + "年度");
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println("表头重建完成,列数: " + cells.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加数据行
|
|
|
+ */
|
|
|
+ private static void addCostTableDataRows(XWPFTable table,
|
|
|
+ List<String> years,
|
|
|
+ List<Map<String, Object>> costItems,
|
|
|
+ int startRowIndex) {
|
|
|
+ System.out.println("添加 " + costItems.size() + " 行数据");
|
|
|
+
|
|
|
+ for (int i = 0; i < costItems.size(); i++) {
|
|
|
+ Map<String, Object> item = costItems.get(i);
|
|
|
+
|
|
|
+ // 创建新行
|
|
|
+ XWPFTableRow dataRow = table.createRow();
|
|
|
+
|
|
|
+ // 确保有足够的单元格
|
|
|
+ int neededCells = 2 + years.size();
|
|
|
+ while (dataRow.getTableCells().size() < neededCells) {
|
|
|
+ dataRow.addNewTableCell();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<XWPFTableCell> cells = dataRow.getTableCells();
|
|
|
+
|
|
|
+ // 设置成本项目
|
|
|
+ String costItemName = (String) item.get("costItemName");
|
|
|
+ setCellText(cells.get(0), costItemName != null ? costItemName : "");
|
|
|
+
|
|
|
+ // 设置行次
|
|
|
+ setCellText(cells.get(1), String.valueOf(i + 1));
|
|
|
+
|
|
|
+ // 设置各年份数据
|
|
|
+ for (int j = 0; j < years.size(); j++) {
|
|
|
+ String year = years.get(j);
|
|
|
+ Map<String, String> yearValues = (Map<String, String>) item.get("yearValues");
|
|
|
+ String value = yearValues != null ? yearValues.get(year) : "";
|
|
|
+ setCellText(cells.get(2 + j), value != null ? value : "0");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加表格说明
|
|
|
+ */
|
|
|
+ private static void addCostTableFooter(XWPFTable table) {
|
|
|
+ XWPFTableRow footerRow = table.createRow();
|
|
|
+
|
|
|
+ // 合并单元格显示说明
|
|
|
+ XWPFTableCell footerCell = footerRow.addNewTableCell();
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取第一行的列数
|
|
|
+ int colCount = table.getRow(0).getTableCells().size();
|
|
|
+ footerCell.getCTTc().addNewTcPr().addNewGridSpan()
|
|
|
+ .setVal(BigInteger.valueOf(colCount));
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("合并单元格失败,但不影响使用: " + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置说明文本
|
|
|
+ XWPFParagraph paragraph = footerCell.addParagraph();
|
|
|
+ XWPFRun run = paragraph.createRun();
|
|
|
+ run.setText("说明:本表应根据行业会计制度及监审项目不同制表,对成本科目及计算方法做相应调整。");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建新的成本表(如果没有找到表格)
|
|
|
+ */
|
|
|
+ private static void createNewCostTable(XWPFDocument document,
|
|
|
+ List<String> years,
|
|
|
+ List<Map<String, Object>> costItems) {
|
|
|
+ XWPFTable newTable = document.createTable();
|
|
|
+
|
|
|
+ // 创建表头行
|
|
|
+ XWPFTableRow headerRow = newTable.getRow(0);
|
|
|
+
|
|
|
+ // 添加表头列
|
|
|
+ XWPFTableCell cell1 = headerRow.getCell(0);
|
|
|
+ setCellText(cell1, "成本项目");
|
|
|
+
|
|
|
+ XWPFTableCell cell2 = headerRow.addNewTableCell();
|
|
|
+ setCellText(cell2, "行次");
|
|
|
+
|
|
|
+ for (String year : years) {
|
|
|
+ XWPFTableCell yearCell = headerRow.addNewTableCell();
|
|
|
+ setCellText(yearCell, year + "年度");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加数据行
|
|
|
+ addCostTableDataRows(newTable, years, costItems, 1);
|
|
|
+
|
|
|
+ // 添加说明行
|
|
|
+ addCostTableFooter(newTable);
|
|
|
+
|
|
|
+ System.out.println("新表格创建完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 工具方法 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置单元格文本
|
|
|
+ */
|
|
|
+ private static void setCellText(XWPFTableCell cell, String text) {
|
|
|
+ // 清除现有内容
|
|
|
+ for (int i = cell.getParagraphs().size() - 1; i >= 0; i--) {
|
|
|
+ cell.removeParagraph(i);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加新内容
|
|
|
+ XWPFParagraph paragraph = cell.addParagraph();
|
|
|
+ paragraph.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run = paragraph.createRun();
|
|
|
+ run.setText(text != null ? text : "");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空单元格内容
|
|
|
+ */
|
|
|
+ private static void clearCellContent(XWPFTableCell cell) {
|
|
|
+ for (int i = cell.getParagraphs().size() - 1; i >= 0; i--) {
|
|
|
+ cell.removeParagraph(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取行文本
|
|
|
+ */
|
|
|
+ private static String getRowText(XWPFTableRow row) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (XWPFTableCell cell : row.getTableCells()) {
|
|
|
+ sb.append(getCellText(cell));
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单元格文本
|
|
|
+ */
|
|
|
+ private static String getCellText(XWPFTableCell cell) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (XWPFParagraph paragraph : cell.getParagraphs()) {
|
|
|
+ String text = paragraph.getText();
|
|
|
+ if (text != null) {
|
|
|
+ sb.append(text);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+}
|