diff --git a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/BookmarkExec.java b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/BookmarkExec.java index d9443fa..197055f 100644 --- a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/BookmarkExec.java +++ b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/BookmarkExec.java @@ -3,22 +3,21 @@ package com.wmyun.farmwork.word.core; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.file.FileNameUtil; -import com.wmyun.farmwork.word.core.enums.BookmarkType; +import com.wmyun.farmwork.word.core.model.BookmarkInfo; import com.wmyun.farmwork.word.core.model.BookmarkReplaceDataModel; -import com.wmyun.farmwork.word.core.model.ext.PictureExData; -import com.wmyun.farmwork.word.core.model.ext.TextExData; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; -import org.apache.poi.util.Units; import org.apache.poi.xwpf.usermodel.*; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText; import org.springframework.stereotype.Component; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; +import java.awt.print.Book; import java.io.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; /** @@ -33,131 +32,45 @@ public class BookmarkExec { public final static String tmpDir = System.getProperty("java.io.tmpdir") + File.separator; + private static final String BOOKMARK_START = "w:bookmarkStart"; + private static final String BOOKMARK_END = "w:bookmarkEnd"; + private static final String MARK_PARAGRAPH = "w:pPr"; + private static final String MARK_RUN = "w:r"; + + private static final String ATTR_BOOKMARK_ID = "w:id"; + private static final String ATTR_BOOKMARK_NAME = "w:name"; + public static File replace(List data, byte[] word, File file) { Map dataMap = data.stream() .collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b)); try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) { - // 收集所有段落 - // 1. 正文中的段落 - List allParagraphs = new ArrayList<>(doc.getParagraphs()); - - // 2. 正文中表格内的段落 - for (XWPFTable table : doc.getTables()) { - for (XWPFTableRow row : table.getRows()) { - for (XWPFTableCell cell : row.getTableCells()) { - allParagraphs.addAll(cell.getParagraphs()); - } - } - } - - // 3. 页眉中的段落及页眉内表格中的段落 - for (XWPFHeader header : doc.getHeaderList()) { - allParagraphs.addAll(header.getParagraphs()); - for (XWPFTable table : header.getTables()) { - for (XWPFTableRow row : table.getRows()) { - for (XWPFTableCell cell : row.getTableCells()) { - allParagraphs.addAll(cell.getParagraphs()); - } - } - } - } - - // 4. 页脚中的段落及页脚内表格中的段落 - for (XWPFFooter footer : doc.getFooterList()) { - allParagraphs.addAll(footer.getParagraphs()); - for (XWPFTable table : footer.getTables()) { - for (XWPFTableRow row : table.getRows()) { - for (XWPFTableCell cell : row.getTableCells()) { - allParagraphs.addAll(cell.getParagraphs()); - } - } - } - } + List allParagraphs = new ArrayList<>(); + collectionAllParagraphs(doc, allParagraphs); for (XWPFParagraph paragraph : allParagraphs) { List runs = paragraph.getRuns(); - List bookmarkStartList = paragraph.getCTP().getBookmarkStartList(); - for (CTBookmark bookmark : bookmarkStartList) { - String name = bookmark.getName(); + List info = queryBookmarkInfo(paragraph); + Map bkMap = info.stream().collect(Collectors.toMap(BookmarkInfo::getBookmarkName, bk -> bk)); + for (Map.Entry bk : bkMap.entrySet()) { + String name = bk.getKey(); + BookmarkInfo bkInfo = bk.getValue(); if (!dataMap.containsKey(name)) { continue; } - XWPFRun run = null; - int idx = 0; - boolean doR = false; - for (int i = 0; i < runs.size(); i++) { - Node previousSibling = runs.get(i).getCTR().getDomNode().getPreviousSibling(); - if (previousSibling != null) { - String nodeName = previousSibling.getNodeName(); - if ("w:bookmarkEnd".equals(nodeName)) { - // end前一个是start - Node bookmarkStartNode = previousSibling.getPreviousSibling(); - if (bookmarkStartNode != null) { - Node nameAttribute = bookmarkStartNode.getAttributes().getNamedItem("w:name"); - // 获取书签名称,确保同一行内多个书签情况能正确处理 - if (nameAttribute != null && name.equals(nameAttribute.getNodeValue())) { - idx = i - 1; - doR = true; - break; - } - } - } - } - } - if (idx < runs.size() && doR) { - if (idx < 0) {// 如果是-1,说明这一段的开头有书签 - // 在本段开头位置插入新的run - run = paragraph.insertNewRun(0); - // 并复制原有开头的run的所有内容,后续只修改文本内容,不改变样式 - run.getCTR().set(runs.get(0).getCTR().copy()); - } else { - run = runs.get(idx); - } - } else if (!runs.isEmpty()){ - // 处理一段末尾的书签(一般不存在这种情况) - run = runs.get(runs.size() - 1); - } else { - run = paragraph.createRun(); - } - if (run == null) { - continue; - } BookmarkReplaceDataModel model = dataMap.get(name); - if (model.getExtData() instanceof TextExData textExData) { - BookmarkType type = textExData.getType(); - - // 处理文本 - if (BookmarkType.TEXT.equals(type)) { - run.setText(textExData.getValue()); - } - + int startIdx = bkInfo.getStartIdx(); + int endIdx = bkInfo.getEndIdx(); + XWPFRun run = runs.get(startIdx); + CTR ctr = run.getCTR(); + List tList = ctr.getTList(); + for (int i = 0; i < ctr.getTList().size(); i++) { + ctr.removeT(i); } - if (model.getExtData() instanceof PictureExData pictureData) { - BookmarkType type = pictureData.getType(); - // 处理图片,以嵌入式插入图片 - if (BookmarkType.PICTURE.equals(type)) { - try { - run.addPicture(new ByteArrayInputStream(pictureData.readAsByteArray()), pictureData.readPictureType(), pictureData.getPictureName(), Units.toEMU(pictureData.getWidth()), Units.toEMU(pictureData.getHeight())); - } catch (InvalidFormatException e) { - log.error(e.getMessage(), e); - throw new RuntimeException(e); - } - } - } - } - - for (XWPFRun run : runs) { - List pictures = run.getEmbeddedPictures(); - for (XWPFPicture picture : pictures) { - String description = picture.getDescription(); - if (dataMap.containsKey(description)) { - BookmarkReplaceDataModel model = dataMap.get(description); - if (model.getExtData() instanceof PictureExData pic) { - String picId = doc.addPictureData(pic.readAsByteArray(), pic.readPictureType()); - picture.getCTPicture().getBlipFill().getBlip().setEmbed(picId); - } - } + CTText newTextNode = ctr.addNewT(); + newTextNode.setStringValue(model.getExtData().getValue()); + for (int i = 0; i < endIdx - startIdx; i++) { + paragraph.removeRun(i); } } } @@ -171,8 +84,119 @@ public class BookmarkExec { doc.write(out); } return outFile; - } catch (IOException | InvalidFormatException e) { + } catch (IOException e) { throw new RuntimeException(e); } } + + /** + * 查询本段中的书签信息 + * @param paragraph 段 + * @return List + */ + private static List queryBookmarkInfo(XWPFParagraph paragraph) { + Set set = new LinkedHashSet<>(); + CTP ctp = paragraph.getCTP(); + List startList = ctp.getBookmarkStartList(); + for (CTBookmark ctBookmark : startList) { + set.add(new BookmarkInfo(ctBookmark.getName(), ctBookmark.getId().toString())); + } + + for (BookmarkInfo info : set) { + queryBookmarkIdx(paragraph, info); + } + + return new ArrayList<>(set); + } + + private static void queryBookmarkIdx(XWPFParagraph paragraph, BookmarkInfo info) { + List runs = paragraph.getRuns(); + boolean foundStart = false; + boolean foundEnd = false; + + // 倒序遍历每个Run,i为原列表的索引 + for (int i = runs.size() - 1; i >= 0; i--) { + XWPFRun run = runs.get(i); + CTR ctr = run.getCTR(); + Node node = ctr.getDomNode(); + Node currentNode = node.getPreviousSibling(); + + // 递归查找前一个兄弟节点 + while (currentNode != null) { + String nodeName = currentNode.getNodeName(); + if (BOOKMARK_START.equals(nodeName)) { + NamedNodeMap attrs = currentNode.getAttributes(); + Node idNode = attrs.getNamedItem(ATTR_BOOKMARK_ID); + String id = idNode != null ? idNode.getNodeValue() : ""; + + // 匹配书签ID或名称 + if (id.equals(info.getBookmarkId())) { + info.setStartIdx(i); // 记录当前Run的索引为起始位置 + foundStart = true; + break; + } + } else if (BOOKMARK_END.equals(nodeName)) { + NamedNodeMap attrs = currentNode.getAttributes(); + Node idNode = attrs.getNamedItem(ATTR_BOOKMARK_ID); + String id = idNode != null ? idNode.getNodeValue() : ""; + + // 匹配书签ID + if (id.equals(info.getBookmarkId())) { + info.setEndIdx(i); // 记录当前Run的索引为结束位置 + foundEnd = true; + break; + } + } + currentNode = currentNode.getPreviousSibling(); // 继续查找前一个节点 + } + + // 如果已找到起始和结束,提前退出循环 + if (foundStart && foundEnd) { + break; + } + } + } + + /** + * 获取文档中所有段落 + * @param doc 文档 + * @param allParagraphs 段落 + */ + private static void collectionAllParagraphs(XWPFDocument doc, List allParagraphs) { + + // 1. 正文中段落 + allParagraphs.addAll(doc.getParagraphs()); + // 2. 正文中表格内的段落 + for (XWPFTable table : doc.getTables()) { + for (XWPFTableRow row : table.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + allParagraphs.addAll(cell.getParagraphs()); + } + } + } + + // 3. 页眉中的段落及页眉内表格中的段落 + for (XWPFHeader header : doc.getHeaderList()) { + allParagraphs.addAll(header.getParagraphs()); + for (XWPFTable table : header.getTables()) { + for (XWPFTableRow row : table.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + allParagraphs.addAll(cell.getParagraphs()); + } + } + } + } + + // 4. 页脚中的段落及页脚内表格中的段落 + for (XWPFFooter footer : doc.getFooterList()) { + allParagraphs.addAll(footer.getParagraphs()); + for (XWPFTable table : footer.getTables()) { + for (XWPFTableRow row : table.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + allParagraphs.addAll(cell.getParagraphs()); + } + } + } + } + } } diff --git a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/BookmarkInfo.java b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/BookmarkInfo.java new file mode 100644 index 0000000..7382b97 --- /dev/null +++ b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/BookmarkInfo.java @@ -0,0 +1,30 @@ +package com.wmyun.farmwork.word.core.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Description: TODO + * @Date: 2025/3/7 11:10 + * @Created: by ZZSLL + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BookmarkInfo { + + private String bookmarkName; + + private String bookmarkId; + + private int startIdx; + + private int endIdx; + + public BookmarkInfo(String bookmarkName, String bookmarkId) { + this.bookmarkName = bookmarkName; + this.bookmarkId = bookmarkId; + } +}