Bläddra i källkod

1.下载文档数据回显BUG
2.监审通知-修改-里面变量值修改后应可保存BUG修改

赵越越 4 veckor sedan
förälder
incheckning
27971342a6

+ 33 - 8
assistMg/src/main/java/com/hotent/project/manager/impl/CostProjectDocumentManagerImpl.java

@@ -34,14 +34,19 @@ import com.hotent.uc.exception.BaseException;
 import com.hotent.uc.manager.UserManager;
 import com.hotent.uc.model.User;
 import com.hotent.uc.util.ContextUtil;
-import com.hotent.util.wordexcelutils.DocumentProcessor;
+import com.hotent.util.HtmlUtils;
+import com.hotent.util.wordexcelutils.*;
 import org.apache.commons.lang.StringUtils;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.apache.tools.ant.util.DateUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -455,9 +460,9 @@ public class CostProjectDocumentManagerImpl extends BaseManagerImpl<CostProjectD
         qw.eq(CostProjectDocument::getProjectId, req.getProjectId());
         qw.eq(CostProjectDocument::getDocumentId, req.getDocumentId());
         qw.eq(CostProjectDocument::getEnterpriseId, req.getEnterpriseId());
-        if (this.count(qw) > 0) {
+        /*if (this.count(qw) > 0) {
             throw new BaseException("该项目监审单位已存在此文书");
-        }
+        }*/
 
         selectCostProjectDocument.setDocumentWhId(req.getDocumentWhId());
         selectCostProjectDocument.setDocumentId(req.getDocumentId());
@@ -466,16 +471,20 @@ public class CostProjectDocumentManagerImpl extends BaseManagerImpl<CostProjectD
 //        类型转换
          BeanUtil.copyProperties(req,selectCostProjectDocument);
         super.update(selectCostProjectDocument);
-        costProjectDocumentFileManager.remove(new LambdaQueryWrapper<CostProjectDocumentFile>().eq(CostProjectDocumentFile::getProjectId,selectCostProjectDocument.getProjectId()));
+        /*costProjectDocumentFileManager.remove(new LambdaQueryWrapper<CostProjectDocumentFile>().eq(CostProjectDocumentFile::getProjectId,selectCostProjectDocument.getProjectId()));
         List<CostProjectDocumentFile> costProjectDocumentFiles = req.getCostProjectDocumentFiles();
         if (costProjectDocumentFiles != null){
             for (CostProjectDocumentFile costProjectDocumentFile : costProjectDocumentFiles) {
                 costProjectDocumentFile.setDocumentId(selectCostProjectDocument.getDocumentId());
                 costProjectDocumentFile.setProjectId(selectCostProjectDocument.getProjectId());
+                //costProjectDocumentFile.setProjectDocumentId(selectCostProjectDocument.);
             }
 
-        }
-        costProjectDocumentFileManager.saveBatch(req.getCostProjectDocumentFiles());
+        }*/
+        req.getCostProjectDocumentFiles().forEach(f->{
+            f.setProjectDocumentId(selectCostProjectDocument.getId());
+        });
+        costProjectDocumentFileManager.updateBatchById(req.getCostProjectDocumentFiles());
     }
 
     @Override
@@ -519,7 +528,7 @@ public class CostProjectDocumentManagerImpl extends BaseManagerImpl<CostProjectD
         //生成文书
         Map<String,String> map = new HashMap<>();
         list.stream().forEach(p->{
-            map.put("{"+p.getOriginalText()+"}",p.getDataValue());
+            map.put("{"+p.getOriginalText()+"}", HtmlUtils.stripHtmlFast(p.getDataValue()) );
         });
         //先处理一种文件
         String templatePath = costDocumentTemplate.getFileUrl().replace(BaseConstant.RESOURCE_PREFIX,"");
@@ -528,7 +537,23 @@ public class CostProjectDocumentManagerImpl extends BaseManagerImpl<CostProjectD
         String fileExtension = FileUploadUtil.getFileExtension(costDocumentTemplate.getFileUrl());
         String fileName = FileUploadUtil.generateFileName(costDocumentTemplate.getDocumentName()+"."+fileExtension);
         String outputPath = FileUploadUtil.generateSavePath(EipConfig.getUploadPath(), fileName, fileExtension);
-        DocumentProcessor.processWordDocument(templatePath,outputPath,map,new LinkedHashMap<>());
+        try (FileInputStream fis = new FileInputStream(templatePath);
+             XWPFDocument document = new XWPFDocument(fis);
+             FileOutputStream fos = new FileOutputStream(outputPath)) {
+            //SimpleStylePreserver.smartReplaceKeepStyle(document,map);
+            //BestPracticeReplacer.replaceTextBestPractice(document,map);
+            //BestPracticeReplacer.applySmartStyles(document);
+            if (!costProjectDocument.getDocumentAlias().equals("cbjstqzldjb")) {
+                SmartTemplateWriter.writeToTemplate(document,map);
+            }else {
+
+            }
+            document.write(fos);
+        } catch (IOException e) {
+            throw new RuntimeException("处理Word文档时出错", e);
+        }
+
+       // DocumentProcessor.processWordDocument(templatePath,outputPath,map,new LinkedHashMap<>());
         return EipConfig.getImgUrl()+FileUploadUtil.getPathFileName(outputPath,fileName);
     }
 

+ 80 - 0
assistMg/src/main/java/com/hotent/util/HtmlUtils.java

@@ -0,0 +1,80 @@
+package com.hotent.util;/**
+ * @program: cbjs-mvue-master
+ * @description:
+ * @author: zhao yue yue
+ * @create: 2025-11-26 15:57
+ */
+
+import org.jsoup.Jsoup;
+
+import java.util.regex.Pattern;
+
+/**
+ *@author: zhao yue yue
+ *@create: 2025-11-26 15:57
+ */
+public class HtmlUtils {
+    private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]+>");
+    private static final Pattern HTML_ENTITY_PATTERN = Pattern.compile("&[a-zA-Z]+;");
+
+    /**
+     * 综合方法:去除所有HTML标签和实体
+     */
+    public static String stripHtml(String html) {
+        if (html == null || html.isEmpty()) {
+            return html;
+        }
+
+        // 方法1:使用JSoup(推荐)
+        return Jsoup.parse(html).text();
+    }
+
+    /**
+     * 快速方法:使用正则表达式
+     */
+    public static String stripHtmlFast(String html) {
+        if (html == null || html.isEmpty()) {
+            return html;
+        }
+
+        String result = HTML_TAG_PATTERN.matcher(html).replaceAll("");
+        result = HTML_ENTITY_PATTERN.matcher(result).replaceAll("");
+        return result.trim();
+    }
+
+    /**
+     * 保留换行格式的方法
+     */
+    public static String stripHtmlKeepNewlines(String html) {
+        if (html == null || html.isEmpty()) {
+            return html;
+        }
+
+        // 替换常见的块级标签为换行符
+        String withNewlines = html.replaceAll("</p>", "\n")
+                .replaceAll("<br[^>]*>", "\n")
+                .replaceAll("</div>", "\n")
+                .replaceAll("</tr>", "\n")
+                .replaceAll("</span>", "\n")
+                .replaceAll("</li>", "\n");
+
+        // 移除所有HTML标签
+        return HTML_TAG_PATTERN.matcher(withNewlines).replaceAll("").trim();
+    }
+
+    /**
+     * 安全方法:处理XSS攻击
+     */
+    /*public static String sanitizeHtml(String html) {
+        if (html == null || html.isEmpty()) {
+            return html;
+        }
+
+        // 使用JSoup进行安全清理
+        return Jsoup.clean(html,
+                Safelist.none()
+                        .addTags("br", "p") // 允许的标签
+                        .addAttributes("a", "href") // 允许的属性
+        );
+    }*/
+}

+ 225 - 0
assistMg/src/main/java/com/hotent/util/wordexcelutils/SimpleTableFiller.java

@@ -0,0 +1,225 @@
+package com.hotent.util.wordexcelutils;/**
+ * @program: cbjs-mvue-master
+ * @description:
+ * @author: zhao yue yue
+ * @create: 2025-11-26 17:03
+ */
+
+import org.apache.poi.xwpf.usermodel.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ *@author: zhao yue yue
+ *@create: 2025-11-26 17:03
+ */
+public class SimpleTableFiller {
+
+    /**
+     * 简化版表格填充
+     */
+    public static void fillTableSimple(XWPFDocument document,
+                                       String organizationName,
+                                       List<Map<String,Object>> rowDataList,
+                                       int totalDocuments,
+                                       int totalPages,
+                                       String generateDate) {
+
+        // 1. 替换基本占位符
+        replaceTextInDocument(document, "[{价格主管部门或成本监审机构}]", organizationName);
+        replaceTextInDocument(document, "[{材料件数}]", String.valueOf(totalDocuments));
+        replaceTextInDocument(document, "[材料页数]", String.valueOf(totalPages));
+        replaceTextInDocument(document, "[登记表生成日期]", generateDate);
+
+        // 2. 填充表格数据
+        fillTableRows(document, rowDataList);
+    }
+
+    /**
+     * 填充表格行数据
+     */
+    private static void fillTableRows(XWPFDocument document, List<Map<String,Object>> rowDataList) {
+        if (rowDataList == null || rowDataList.isEmpty()) return;
+
+        XWPFTable table = document.getTables().get(0);
+        if (table == null) return;
+
+        // 找到模板行(包含[{序号}]等占位符的行)
+        int templateRowIndex = findTemplateRowIndex(table);
+        if (templateRowIndex == -1) return;
+
+        XWPFTableRow templateRow = table.getRows().get(templateRowIndex);
+
+        // 填充数据行
+        for (int i = 0; i < rowDataList.size(); i++) {
+            Map<String,Object> data = rowDataList.get(i);
+
+            // 如果是第一行数据,直接替换模板行
+            if (i == 0) {
+                fillExistingRow(templateRow, data, i + 1);
+            } else {
+                // 后续行创建新行
+                addNewDataRow(table, templateRow, data, i + 1);
+            }
+        }
+
+        // 如果数据行数少于模板行,移除多余的模板行
+        if (rowDataList.size() == 1) {
+            // 只保留一个数据行,移除其他空行
+            cleanupEmptyRows(table, templateRowIndex + 1);
+        }
+    }
+
+    /**
+     * 找到模板行索引
+     */
+    private static int findTemplateRowIndex(XWPFTable table) {
+        for (int i = 0; i < table.getRows().size(); i++) {
+            XWPFTableRow row = table.getRows().get(i);
+            if (containsPlaceholder(row, "[{序号}]")) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * 检查行是否包含占位符
+     */
+    private static boolean containsPlaceholder(XWPFTableRow row, String placeholder) {
+        for (XWPFTableCell cell : row.getTableCells()) {
+            for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                if (paragraph.getText() != null && paragraph.getText().contains(placeholder)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 填充现有行
+     */
+    private static void fillExistingRow(XWPFTableRow row, Map<String,Object> data, int sequence) {
+        List<XWPFTableCell> cells = row.getTableCells();
+        if (cells.size() < 4) return;
+
+        // 替换占位符
+        replaceInCell(cells.get(0), "[{序号}]", String.valueOf(sequence));
+        replaceInCell(cells.get(1), "[{资料名称}]",String.valueOf(data.get("documentName")));
+        replaceInCell(cells.get(2), "[{页数}]", Integer.parseInt(data.get("pageCount").toString())  > 0 ? String.valueOf(Integer.parseInt(data.get("pageCount").toString())) : "");
+        replaceInCell(cells.get(3), "[{备注}]", String.valueOf(data.get("remark")) != null ? String.valueOf(data.get("remark")) : "");
+    }
+
+    /**
+     * 添加新数据行
+     */
+    private static void addNewDataRow(XWPFTable table, XWPFTableRow templateRow, Map<String,Object> data , int sequence) {
+        // 创建新行
+        XWPFTableRow newRow = table.createRow();
+
+        // 复制模板行的单元格结构
+        List<XWPFTableCell> templateCells = templateRow.getTableCells();
+        List<XWPFTableCell> newCells = newRow.getTableCells();
+
+        // 确保单元格数量一致
+        while (newCells.size() < templateCells.size()) {
+            newRow.addNewTableCell();
+            newCells = newRow.getTableCells();
+        }
+
+        // 填充数据
+        setCellText(newCells.get(0), String.valueOf(sequence));
+        setCellText(newCells.get(1), String.valueOf(data.get("documentName")));
+        setCellText(newCells.get(2), Integer.parseInt(data.get("pageCount").toString())  > 0 ? String.valueOf(Integer.parseInt(data.get("pageCount").toString())) : "");
+        setCellText(newCells.get(3),  String.valueOf(data.get("remark")) != null ? String.valueOf(data.get("remark")) : "");
+    }
+
+    /**
+     * 清理空行
+     */
+    private static void cleanupEmptyRows(XWPFTable table, int startIndex) {
+        // 从指定索引开始移除空行
+        for (int i = table.getRows().size() - 1; i >= startIndex; i--) {
+            XWPFTableRow row = table.getRows().get(i);
+            if (isRowEmpty(row)) {
+                table.removeRow(i);
+            }
+        }
+    }
+
+    /**
+     * 检查行是否为空
+     */
+    private static boolean isRowEmpty(XWPFTableRow row) {
+        for (XWPFTableCell cell : row.getTableCells()) {
+            for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                if (paragraph.getText() != null && !paragraph.getText().trim().isEmpty()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 替换单元格中的文本
+     */
+    private static void replaceInCell(XWPFTableCell cell, String placeholder, String value) {
+        for (XWPFParagraph paragraph : cell.getParagraphs()) {
+            for (XWPFRun run : paragraph.getRuns()) {
+                String text = run.getText(0);
+                if (text != null && text.contains(placeholder)) {
+                    run.setText(text.replace(placeholder, value), 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置单元格文本
+     */
+    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();
+        XWPFRun run = paragraph.createRun();
+        run.setText(text);
+        paragraph.setAlignment(ParagraphAlignment.CENTER);
+    }
+
+    /**
+     * 替换文档中的文本
+     */
+    private static void replaceTextInDocument(XWPFDocument document, String findText, String replaceText) {
+        for (XWPFParagraph paragraph : document.getParagraphs()) {
+            replaceInParagraph(paragraph, findText, replaceText);
+        }
+
+        for (XWPFTable table : document.getTables()) {
+            for (XWPFTableRow row : table.getRows()) {
+                for (XWPFTableCell cell : row.getTableCells()) {
+                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                        replaceInParagraph(paragraph, findText, replaceText);
+                    }
+                }
+            }
+        }
+    }
+
+    private static void replaceInParagraph(XWPFParagraph paragraph, String findText, String replaceText) {
+        for (XWPFRun run : paragraph.getRuns()) {
+            String text = run.getText(0);
+            if (text != null && text.contains(findText)) {
+                run.setText(text.replace(findText, replaceText), 0);
+            }
+        }
+    }
+
+
+}

+ 354 - 0
assistMg/src/main/java/com/hotent/util/wordexcelutils/SmartTemplateWriter.java

@@ -0,0 +1,354 @@
+package com.hotent.util.wordexcelutils;/**
+ * @program: cbjs-mvue-master
+ * @description:
+ * @author: zhao yue yue
+ * @create: 2025-11-26 16:47
+ */
+
+import org.apache.poi.xwpf.usermodel.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *@author: zhao yue yue
+ *@create: 2025-11-26 16:47
+ */
+public class SmartTemplateWriter {
+
+    /**
+     * 智能写入:保证数据写入 + 保持样式
+     */
+    public static boolean writeToTemplate(XWPFDocument templateDoc,
+                                          Map<String, String> dataMap) {
+        if (templateDoc == null || dataMap == null) return false;
+
+        boolean allSuccess = true;
+
+        // 遍历所有占位符
+        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
+            String placeholder = entry.getKey(); // 如 ${name}
+            String value = entry.getValue();
+
+            boolean success = replacePlaceholderSmart(templateDoc, placeholder, value);
+            if (!success) {
+                System.err.println("警告: 占位符 " + placeholder + " 替换失败");
+                allSuccess = false;
+            }
+        }
+
+        return allSuccess;
+    }
+
+    /**
+     * 智能替换占位符
+     */
+    private static boolean replacePlaceholderSmart(XWPFDocument doc, String placeholder, String value) {
+        boolean found = false;
+
+        // 1. 首先在段落中查找
+        for (XWPFParagraph paragraph : doc.getParagraphs()) {
+            if (replaceInParagraphSmart(paragraph, placeholder, value)) {
+                found = true;
+            }
+        }
+
+        // 2. 在表格中查找
+        for (XWPFTable table : doc.getTables()) {
+            for (XWPFTableRow row : table.getRows()) {
+                for (XWPFTableCell cell : row.getTableCells()) {
+                    for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                        if (replaceInParagraphSmart(paragraph, placeholder, value)) {
+                            found = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // 3. 如果还没找到,使用强制写入(最后手段)
+        if (!found) {
+            found = forceWriteToFirstAvailable(doc, placeholder, value);
+        }
+
+        return found;
+    }
+
+    /**
+     * 在段落中智能替换
+     */
+    private static boolean replaceInParagraphSmart(XWPFParagraph paragraph, String placeholder, String value) {
+        List<XWPFRun> runs = paragraph.getRuns();
+        boolean foundInThisParagraph = false;
+
+        // 方法1:在单个Run中直接替换(最佳情况)
+        for (XWPFRun run : runs) {
+            String text = run.getText(0);
+            if (text != null && text.contains(placeholder)) {
+                replaceInSingleRun(run, placeholder, value);
+                foundInThisParagraph = true;
+                break; // 找到一个就退出,避免重复替换
+            }
+        }
+
+        // 方法2:跨Run替换
+        if (!foundInThisParagraph) {
+            String fullText = paragraph.getText();
+            if (fullText != null && fullText.contains(placeholder)) {
+                foundInThisParagraph = replaceAcrossRuns(paragraph, placeholder, value);
+            }
+        }
+
+        return foundInThisParagraph;
+    }
+
+    /**
+     * 在单个Run中替换(保持格式)
+     */
+    private static void replaceInSingleRun(XWPFRun run, String placeholder, String value) {
+        try {
+            String text = run.getText(0);
+            if (text != null && text.contains(placeholder)) {
+                // 保存原始格式
+                RunStyle originalStyle = saveRunStyle(run);
+
+                // 执行替换
+                String newText = text.replace(placeholder, value);
+                run.setText(newText, 0);
+
+                // 恢复格式
+                restoreRunStyle(run, originalStyle);
+
+                System.out.println("成功在单个Run中替换: " + placeholder);
+            }
+        } catch (Exception e) {
+            System.err.println("单个Run替换失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 跨Run替换(复杂但必要)
+     */
+    private static boolean replaceAcrossRuns(XWPFParagraph paragraph, String placeholder, String value) {
+        try {
+            List<XWPFRun> runs = paragraph.getRuns();
+            if (runs.isEmpty()) return false;
+
+            // 构建完整文本并记录Run信息
+            StringBuilder fullText = new StringBuilder();
+            List<RunInfo> runInfos = new ArrayList<>();
+
+            int currentPos = 0;
+            for (XWPFRun run : runs) {
+                String text = run.getText(0);
+                if (text == null) text = "";
+
+                RunInfo info = new RunInfo();
+                info.run = run;
+                info.text = text;
+                info.startPos = currentPos;
+                info.endPos = currentPos + text.length();
+                runInfos.add(info);
+
+                fullText.append(text);
+                currentPos += text.length();
+            }
+
+            String completeText = fullText.toString();
+            int startIndex = completeText.indexOf(placeholder);
+
+            if (startIndex == -1) return false;
+
+            int endIndex = startIndex + placeholder.length();
+
+            // 找到受影响的Run
+            List<RunInfo> affectedRuns = new ArrayList<>();
+            for (RunInfo info : runInfos) {
+                if (info.endPos > startIndex && info.startPos < endIndex) {
+                    affectedRuns.add(info);
+                }
+            }
+
+            if (affectedRuns.isEmpty()) return false;
+
+            // 执行跨Run替换
+            return executeCrossRunReplacement(paragraph, runInfos, affectedRuns,
+                    startIndex, endIndex, placeholder, value);
+
+        } catch (Exception e) {
+            System.err.println("跨Run替换失败: " + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 执行跨Run替换
+     */
+    private static boolean executeCrossRunReplacement(XWPFParagraph paragraph,
+                                                      List<RunInfo> allRunInfos,
+                                                      List<RunInfo> affectedRuns,
+                                                      int startIndex, int endIndex,
+                                                      String placeholder, String value) {
+        try {
+            // 保存第一个受影响Run的格式作为基准
+            XWPFRun baseRun = affectedRuns.get(0).run;
+            RunStyle baseStyle = saveRunStyle(baseRun);
+
+            // 构建新文本
+            String fullText = getParagraphFullText(paragraph);
+            String newFullText = fullText.replace(placeholder, value);
+
+            // 清除段落内容但保留段落属性
+            clearParagraphContent(paragraph);
+
+            // 重新创建Run并设置文本
+            XWPFRun newRun = paragraph.createRun();
+            newRun.setText(newFullText);
+
+            // 应用基准格式
+            restoreRunStyle(newRun, baseStyle);
+
+            System.out.println("跨Run替换成功: " + placeholder);
+            return true;
+
+        } catch (Exception e) {
+            System.err.println("执行跨Run替换失败: " + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 强制写入:最后手段
+     */
+    private static boolean forceWriteToFirstAvailable(XWPFDocument doc, String placeholder, String value) {
+        System.out.println("使用强制写入: " + placeholder);
+
+        // 找到第一个可用的段落
+        for (XWPFParagraph paragraph : doc.getParagraphs()) {
+            if (isParagraphWritable(paragraph)) {
+                return forceWriteToParagraph(paragraph, placeholder, value);
+            }
+        }
+
+        // 如果段落都不可写,创建新段落
+        return createNewParagraphWithData(doc, value);
+    }
+
+    /**
+     * 强制写入到段落
+     */
+    private static boolean forceWriteToParagraph(XWPFParagraph paragraph, String placeholder, String value) {
+        try {
+            // 保存第一个Run的格式
+            RunStyle style = null;
+            if (!paragraph.getRuns().isEmpty()) {
+                style = saveRunStyle(paragraph.getRuns().get(0));
+            }
+
+            // 清除内容
+            clearParagraphContent(paragraph);
+
+            // 写入新内容
+            XWPFRun newRun = paragraph.createRun();
+            newRun.setText(value);
+
+            // 恢复格式
+            if (style != null) {
+                restoreRunStyle(newRun, style);
+            }
+
+            return true;
+
+        } catch (Exception e) {
+            System.err.println("强制写入失败: " + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 创建新段落并写入数据
+     */
+    private static boolean createNewParagraphWithData(XWPFDocument doc, String value) {
+        try {
+            XWPFParagraph newParagraph = doc.createParagraph();
+            XWPFRun newRun = newParagraph.createRun();
+            newRun.setText(value);
+            return true;
+        } catch (Exception e) {
+            System.err.println("创建新段落失败: " + e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 工具方法
+     */
+    private static String getParagraphFullText(XWPFParagraph paragraph) {
+        StringBuilder sb = new StringBuilder();
+        for (XWPFRun run : paragraph.getRuns()) {
+            String text = run.getText(0);
+            if (text != null) {
+                sb.append(text);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static void clearParagraphContent(XWPFParagraph paragraph) {
+        for (XWPFRun run : paragraph.getRuns()) {
+            run.setText("", 0);
+        }
+    }
+
+    private static boolean isParagraphWritable(XWPFParagraph paragraph) {
+        return paragraph != null;
+    }
+
+    // 保存和恢复Run样式的方法(使用之前的安全版本)
+    private static RunStyle saveRunStyle(XWPFRun run) {
+        RunStyle style = new RunStyle();
+        if (run == null) return style;
+
+        style.bold = run.isBold();
+        style.italic = run.isItalic();
+        style.fontFamily = run.getFontFamily();
+        style.fontSize = run.getFontSize();
+        style.color = run.getColor();
+        style.underline = run.getUnderline();
+        return style;
+    }
+
+    private static void restoreRunStyle(XWPFRun run, RunStyle style) {
+        if (run == null || style == null) return;
+
+        run.setBold(style.bold);
+        run.setItalic(style.italic);
+        if (style.fontFamily != null) run.setFontFamily(style.fontFamily);
+        if (style.fontSize != null) run.setFontSize(style.fontSize);
+        if (style.color != null) run.setColor(style.color);
+        if (style.underline != null) run.setUnderline(style.underline);
+    }
+
+    /**
+     * 辅助类
+     */
+    static class RunStyle {
+        boolean bold;
+        boolean italic;
+        String fontFamily;
+        Integer fontSize;
+        String color;
+        UnderlinePatterns underline;
+    }
+
+    static class RunInfo {
+        XWPFRun run;
+        String text;
+        int startPos;
+        int endPos;
+    }
+
+
+
+}