feat: bookmark replace
This commit is contained in:
parent
bfab299485
commit
6e32047bcd
@ -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<BookmarkReplaceDataModel> data, byte[] word, File file) {
|
||||
Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
|
||||
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
|
||||
try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) {
|
||||
// 收集所有段落
|
||||
|
||||
// 1. 正文中的段落
|
||||
List<XWPFParagraph> 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<XWPFParagraph> allParagraphs = new ArrayList<>();
|
||||
collectionAllParagraphs(doc, allParagraphs);
|
||||
|
||||
for (XWPFParagraph paragraph : allParagraphs) {
|
||||
List<XWPFRun> runs = paragraph.getRuns();
|
||||
List<CTBookmark> bookmarkStartList = paragraph.getCTP().getBookmarkStartList();
|
||||
for (CTBookmark bookmark : bookmarkStartList) {
|
||||
String name = bookmark.getName();
|
||||
List<BookmarkInfo> info = queryBookmarkInfo(paragraph);
|
||||
Map<String, BookmarkInfo> bkMap = info.stream().collect(Collectors.toMap(BookmarkInfo::getBookmarkName, bk -> bk));
|
||||
for (Map.Entry<String, BookmarkInfo> 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<CTText> 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<XWPFPicture> 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<BookmarkInfo>
|
||||
*/
|
||||
private static List<BookmarkInfo> queryBookmarkInfo(XWPFParagraph paragraph) {
|
||||
Set<BookmarkInfo> set = new LinkedHashSet<>();
|
||||
CTP ctp = paragraph.getCTP();
|
||||
List<CTBookmark> 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<XWPFRun> 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<XWPFParagraph> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user