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.FileUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.io.file.FileNameUtil;
|
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.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 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.apache.poi.xwpf.usermodel.*;
|
||||||
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
|
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.springframework.stereotype.Component;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import java.awt.print.Book;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,131 +32,45 @@ public class BookmarkExec {
|
|||||||
|
|
||||||
public final static String tmpDir = System.getProperty("java.io.tmpdir") + File.separator;
|
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) {
|
public static File replace(List<BookmarkReplaceDataModel> data, byte[] word, File file) {
|
||||||
Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
|
Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
|
||||||
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
|
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
|
||||||
try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) {
|
try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) {
|
||||||
// 收集所有段落
|
|
||||||
|
|
||||||
// 1. 正文中的段落
|
List<XWPFParagraph> allParagraphs = new ArrayList<>();
|
||||||
List<XWPFParagraph> allParagraphs = new ArrayList<>(doc.getParagraphs());
|
collectionAllParagraphs(doc, allParagraphs);
|
||||||
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (XWPFParagraph paragraph : allParagraphs) {
|
for (XWPFParagraph paragraph : allParagraphs) {
|
||||||
List<XWPFRun> runs = paragraph.getRuns();
|
List<XWPFRun> runs = paragraph.getRuns();
|
||||||
List<CTBookmark> bookmarkStartList = paragraph.getCTP().getBookmarkStartList();
|
List<BookmarkInfo> info = queryBookmarkInfo(paragraph);
|
||||||
for (CTBookmark bookmark : bookmarkStartList) {
|
Map<String, BookmarkInfo> bkMap = info.stream().collect(Collectors.toMap(BookmarkInfo::getBookmarkName, bk -> bk));
|
||||||
String name = bookmark.getName();
|
for (Map.Entry<String, BookmarkInfo> bk : bkMap.entrySet()) {
|
||||||
|
String name = bk.getKey();
|
||||||
|
BookmarkInfo bkInfo = bk.getValue();
|
||||||
if (!dataMap.containsKey(name)) {
|
if (!dataMap.containsKey(name)) {
|
||||||
continue;
|
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);
|
BookmarkReplaceDataModel model = dataMap.get(name);
|
||||||
if (model.getExtData() instanceof TextExData textExData) {
|
int startIdx = bkInfo.getStartIdx();
|
||||||
BookmarkType type = textExData.getType();
|
int endIdx = bkInfo.getEndIdx();
|
||||||
|
XWPFRun run = runs.get(startIdx);
|
||||||
// 处理文本
|
CTR ctr = run.getCTR();
|
||||||
if (BookmarkType.TEXT.equals(type)) {
|
List<CTText> tList = ctr.getTList();
|
||||||
run.setText(textExData.getValue());
|
for (int i = 0; i < ctr.getTList().size(); i++) {
|
||||||
}
|
ctr.removeT(i);
|
||||||
|
|
||||||
}
|
}
|
||||||
if (model.getExtData() instanceof PictureExData pictureData) {
|
CTText newTextNode = ctr.addNewT();
|
||||||
BookmarkType type = pictureData.getType();
|
newTextNode.setStringValue(model.getExtData().getValue());
|
||||||
// 处理图片,以嵌入式插入图片
|
for (int i = 0; i < endIdx - startIdx; i++) {
|
||||||
if (BookmarkType.PICTURE.equals(type)) {
|
paragraph.removeRun(i);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,8 +84,119 @@ public class BookmarkExec {
|
|||||||
doc.write(out);
|
doc.write(out);
|
||||||
}
|
}
|
||||||
return outFile;
|
return outFile;
|
||||||
} catch (IOException | InvalidFormatException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(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