feat: bookmark replace
This commit is contained in:
parent
dfee41ef5a
commit
5c8468260a
@ -3,8 +3,13 @@ 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.google.common.collect.Maps;
|
||||
import com.wmyun.farmwork.word.core.model.BookmarkInfo;
|
||||
import com.wmyun.farmwork.word.core.model.BookmarkReplaceDataModel;
|
||||
import com.wmyun.farmwork.word.core.model.TableBookmarkInfo;
|
||||
import com.wmyun.farmwork.word.core.model.ext.ListExData;
|
||||
import com.wmyun.farmwork.word.core.model.ext.TableExData;
|
||||
import com.wmyun.farmwork.word.core.model.ext.TextExData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.xwpf.usermodel.*;
|
||||
import org.apache.xmlbeans.XmlCursor;
|
||||
@ -38,15 +43,31 @@ public class BookmarkExec {
|
||||
private static final String ATTR_BOOKMARK_ID = "w:id";
|
||||
private static final String ATTR_BOOKMARK_NAME = "w:name";
|
||||
|
||||
private static final String TABLE_ROW_BOOKMARK_START = "w:colFirst";
|
||||
private static final String TABLE_ROW_BOOKMARK_END = "w:colLast";
|
||||
|
||||
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))) {
|
||||
|
||||
// 收集文档中所有段落
|
||||
List<XWPFParagraph> allParagraphs = new ArrayList<>();
|
||||
collectionAllParagraphs(doc, allParagraphs);
|
||||
|
||||
// 列表书签
|
||||
List<String> listMark = new ArrayList<>();
|
||||
// 表格内书签信息
|
||||
List<TableBookmarkInfo> tableBookmarkInfos = queryBookmarkInTable(doc);
|
||||
// 所有待替换数据
|
||||
Map<String, BookmarkReplaceDataModel> allDataMap = data.stream()
|
||||
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
|
||||
// 常规替换数据
|
||||
Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
|
||||
.filter(d -> {
|
||||
List<String> bookmarks = tableBookmarkInfos.stream()
|
||||
.flatMap(info -> info.getBookmark().stream())
|
||||
.toList();
|
||||
return bookmarks.contains(d.getName());
|
||||
}) // 过滤表格中需要整行替换的书签
|
||||
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
|
||||
|
||||
// 处理段落中书签
|
||||
for (XWPFParagraph paragraph : allParagraphs) {
|
||||
@ -76,7 +97,9 @@ public class BookmarkExec {
|
||||
ctr.removeT(i);
|
||||
}
|
||||
CTText newTextNode = ctr.addNewT();
|
||||
newTextNode.setStringValue(model.getExtData().getValue());
|
||||
if (model.getExtData() instanceof TextExData exData) {
|
||||
newTextNode.setStringValue(exData.getValue());
|
||||
}
|
||||
|
||||
// 删除其余的run
|
||||
for (int i = startIdx + 1; i < endIdx; i++) {
|
||||
@ -92,10 +115,26 @@ public class BookmarkExec {
|
||||
|
||||
// 处理列表
|
||||
for (String mk : listMark) {
|
||||
handleListContent(doc, new String[]{"行1", "行2", "行3", "行4", "行5"}, mk);
|
||||
if (allDataMap.containsKey(mk)) {
|
||||
BookmarkReplaceDataModel model = allDataMap.get(mk);
|
||||
if (model.getExtData() instanceof ListExData exData) {
|
||||
handleListContent(doc, exData.getValue(), mk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表格
|
||||
for (TableBookmarkInfo tableBkInfo : tableBookmarkInfos) {
|
||||
String masterBookmark = tableBkInfo.getMasterBookmark();
|
||||
if (allDataMap.containsKey(masterBookmark)) {
|
||||
BookmarkReplaceDataModel model = allDataMap.get(masterBookmark);
|
||||
if (model.getExtData() instanceof TableExData exData) {
|
||||
handleTableContent(doc, tableBkInfo, exData.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
File outFile = FileUtil.newFile(tmpDir + "gen/" + FileNameUtil.getName(file));
|
||||
File parentDir = outFile.getParentFile();
|
||||
if (!parentDir.exists()) {
|
||||
@ -110,6 +149,126 @@ public class BookmarkExec {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理word中的表格,删除模板行,新增行
|
||||
*/
|
||||
private static void handleTableContent(XWPFDocument doc, TableBookmarkInfo tableBkInfo, List<Map<String, String>> values) {
|
||||
List<XWPFTable> tables = doc.getTables();
|
||||
int tableIdx = tableBkInfo.getTableIdx();
|
||||
XWPFTable table = tables.get(tableIdx);
|
||||
int templateRowIdx = tableBkInfo.getTemplateRowIdx();
|
||||
|
||||
// 获取模板行
|
||||
XWPFTableRow templateRow = table.getRow(templateRowIdx);
|
||||
|
||||
// 提取各列的书签名称
|
||||
List<String> columnKeys = new ArrayList<>();
|
||||
for (XWPFTableCell cell : templateRow.getTableCells()) {
|
||||
String bookmarkName = null;
|
||||
// 遍历单元格的段落,查找第一个书签
|
||||
for (XWPFParagraph paragraph : cell.getParagraphs()) {
|
||||
for (CTBookmark bookmark : paragraph.getCTP().getBookmarkStartList()) {
|
||||
bookmarkName = bookmark.getName();
|
||||
break; // 取第一个书签
|
||||
}
|
||||
if (bookmarkName != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bookmarkName == null) {
|
||||
throw new IllegalArgumentException("模板行的单元格中未找到书签");
|
||||
}
|
||||
columnKeys.add(bookmarkName);
|
||||
}
|
||||
|
||||
// 插入新行并填充数据
|
||||
int currentInsertPos = templateRowIdx; // 初始插入位置为模板行的索引
|
||||
for (Map<String, String> dataRow : values) {
|
||||
// 在指定位置插入新行
|
||||
XWPFTableRow newRow = table.insertNewTableRow(currentInsertPos);
|
||||
// 复制模板行的每个单元格的格式并填充数据
|
||||
for (int i = 0; i < templateRow.getTableCells().size(); i++) {
|
||||
XWPFTableCell templateCell = templateRow.getCell(i);
|
||||
XWPFTableCell newCell = newRow.addNewTableCell();
|
||||
|
||||
// 复制单元格样式
|
||||
CTTcPr templateTcPr = templateCell.getCTTc().getTcPr();
|
||||
if (templateTcPr != null) {
|
||||
newCell.getCTTc().setTcPr((CTTcPr) templateTcPr.copy());
|
||||
}
|
||||
|
||||
// 设置单元格内容
|
||||
String key = columnKeys.get(i);
|
||||
String value = dataRow.getOrDefault(key, "");
|
||||
// 清除单元格原有内容
|
||||
newCell.removeParagraph(0); // 移除默认创建的段落
|
||||
XWPFParagraph paragraph = newCell.addParagraph();
|
||||
XWPFRun run = paragraph.createRun();
|
||||
run.setText(value);
|
||||
}
|
||||
currentInsertPos++; // 插入位置后移
|
||||
}
|
||||
|
||||
// 删除模板行(此时模板行的新索引为原索引加上插入的行数)
|
||||
int finalTemplateRowIndex = templateRowIdx + values.size();
|
||||
table.removeRow(finalTemplateRowIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询word中表格中需要整行替换的书签
|
||||
*/
|
||||
private static List<TableBookmarkInfo> queryBookmarkInTable(XWPFDocument doc) {
|
||||
|
||||
List<TableBookmarkInfo> list = new ArrayList<>();
|
||||
List<XWPFTable> tables = doc.getTables();
|
||||
|
||||
// 表格
|
||||
for (XWPFTable table : tables) {
|
||||
int idx = tables.indexOf(table);
|
||||
TableBookmarkInfo info = new TableBookmarkInfo();
|
||||
List<String> filterMark = new ArrayList<>();
|
||||
|
||||
// 行
|
||||
for (XWPFTableRow row : table.getRows()) {
|
||||
int currentRowIdx = table.getRows().indexOf(row);
|
||||
|
||||
// 单元格
|
||||
for (XWPFTableCell cell : row.getTableCells()) {
|
||||
boolean isReplaceRow = false;
|
||||
List<XWPFParagraph> paragraphs = cell.getParagraphs();
|
||||
for (XWPFParagraph paragraph : paragraphs) {
|
||||
CTP ctp = paragraph.getCTP();
|
||||
List<CTBookmark> bookmark = ctp.getBookmarkStartList();
|
||||
|
||||
// 单元格内书签
|
||||
for (CTBookmark bk : bookmark) {
|
||||
NamedNodeMap attr = bk.getDomNode().getAttributes();
|
||||
Optional<Node> startAttrOpt = Optional.ofNullable(attr.getNamedItem(TABLE_ROW_BOOKMARK_START));
|
||||
Optional<Node> endAttrOpt = Optional.ofNullable(attr.getNamedItem(TABLE_ROW_BOOKMARK_END));
|
||||
|
||||
// 是否是整行是书签,如果是,标记这一行为模板
|
||||
if (startAttrOpt.map(Node::getNodeValue).isPresent() && endAttrOpt.map(Node::getNodeValue).isPresent()) {
|
||||
filterMark.add(bk.getName());
|
||||
isReplaceRow = true;
|
||||
info.setTableIdx(idx);
|
||||
info.setTemplateRowIdx(currentRowIdx);
|
||||
info.setMasterBookmark(bk.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 收集模板行的所有书签
|
||||
if (isReplaceRow) {
|
||||
filterMark.addAll(bookmark.stream().map(CTBookmark::getName).collect(Collectors.toSet()));
|
||||
info.setBookmark(filterMark);
|
||||
list.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询本段中的书签信息
|
||||
*
|
||||
@ -138,6 +297,9 @@ public class BookmarkExec {
|
||||
return new ArrayList<>(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找书签在段落中包含的位置
|
||||
*/
|
||||
private static void queryBookmarkIdx(XWPFParagraph paragraph, BookmarkInfo info) {
|
||||
List<XWPFRun> runs = paragraph.getRuns();
|
||||
boolean foundStart = false;
|
||||
@ -198,9 +360,6 @@ public class BookmarkExec {
|
||||
|
||||
/**
|
||||
* 获取文档中所有段落
|
||||
*
|
||||
* @param doc 文档
|
||||
* @param allParagraphs 段落
|
||||
*/
|
||||
private static void collectionAllParagraphs(XWPFDocument doc, List<XWPFParagraph> allParagraphs) {
|
||||
|
||||
@ -241,9 +400,9 @@ public class BookmarkExec {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理行新增
|
||||
* 处理列表行新增
|
||||
*/
|
||||
public static void handleListContent(XWPFDocument doc, String[] newListItems, String bookmarkName) {
|
||||
public static void handleListContent(XWPFDocument doc, List<String> newListItems, String bookmarkName) {
|
||||
// 查找书签所在的段落
|
||||
XWPFParagraph bookmarkParagraph = findBookmarkParagraph(doc, bookmarkName);
|
||||
if (bookmarkParagraph == null) {
|
||||
@ -316,7 +475,7 @@ public class BookmarkExec {
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertNewListItems(XWPFDocument doc, String[] items, int insertPos,
|
||||
private static void insertNewListItems(XWPFDocument doc, List<String> items, int insertPos,
|
||||
BigInteger numId, BigInteger ilvl, XWPFParagraph originalPara, XWPFRun xwpfRun) {
|
||||
// 获取插入位置的游标锚点
|
||||
XmlCursor cursor = findInsertCursor(doc, insertPos);
|
||||
|
@ -18,7 +18,11 @@ public enum BookmarkType {
|
||||
|
||||
PICTURE("PICTURE"),
|
||||
|
||||
PICTURE_DESC("PICTURE_DESC");
|
||||
PICTURE_DESC("PICTURE_DESC"),
|
||||
|
||||
TABLE("TABLE"),
|
||||
|
||||
LIST("LIST"),;
|
||||
|
||||
private final String type;
|
||||
|
||||
|
@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.wmyun.farmwork.word.core.enums.BookmarkType;
|
||||
import com.wmyun.farmwork.word.core.model.ext.ListExData;
|
||||
import com.wmyun.farmwork.word.core.model.ext.PictureExData;
|
||||
import com.wmyun.farmwork.word.core.model.ext.TableExData;
|
||||
import com.wmyun.farmwork.word.core.model.ext.TextExData;
|
||||
import lombok.Data;
|
||||
|
||||
@ -23,12 +25,12 @@ import lombok.Data;
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = TextExData.class, name = "TEXT"),
|
||||
@JsonSubTypes.Type(value = PictureExData.class, name = "PICTURE"),
|
||||
@JsonSubTypes.Type(value = PictureExData.class, name = "PICTURE_DESC")
|
||||
@JsonSubTypes.Type(value = PictureExData.class, name = "PICTURE_DESC"),
|
||||
@JsonSubTypes.Type(value = ListExData.class, name = "LIST"),
|
||||
@JsonSubTypes.Type(value = TableExData.class, name = "TABLE")
|
||||
})
|
||||
public class AbstractExData {
|
||||
|
||||
@JsonProperty("type")
|
||||
private BookmarkType type;
|
||||
|
||||
private String value;
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ public class BookmarkInfo {
|
||||
// 是否是列表
|
||||
private boolean listMark;
|
||||
|
||||
// 是否在表格中
|
||||
private boolean tableMark;
|
||||
|
||||
public BookmarkInfo(String bookmarkName, String bookmarkId) {
|
||||
this.bookmarkName = bookmarkName;
|
||||
this.bookmarkId = bookmarkId;
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.wmyun.farmwork.word.core.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @Date: 2025/3/7 16:35
|
||||
* @Created: by ZZSLL
|
||||
*/
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class TableBookmarkInfo {
|
||||
|
||||
// 表格在word中的index
|
||||
private int tableIdx;
|
||||
|
||||
private String masterBookmark;
|
||||
|
||||
private List<String> bookmark;
|
||||
|
||||
private int templateRowIdx;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.wmyun.farmwork.word.core.model.ext;
|
||||
|
||||
import com.wmyun.farmwork.word.core.model.AbstractExData;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @Date: 2025/3/7 16:52
|
||||
* @Created: by ZZSLL
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class ListExData extends AbstractExData {
|
||||
|
||||
private List<String> value;
|
||||
|
||||
}
|
@ -28,16 +28,18 @@ public class PictureExData extends AbstractExData {
|
||||
|
||||
private PictureProvideDataType dataType;
|
||||
|
||||
private String value;
|
||||
|
||||
@SneakyThrows
|
||||
public byte[] readAsByteArray() {
|
||||
if (PictureProvideDataType.BASE64.equals(dataType)) {
|
||||
return Base64.getDecoder().decode(this.getValue());
|
||||
return Base64.getDecoder().decode(value);
|
||||
}
|
||||
if (PictureProvideDataType.DOWNLOAD_URL.equals(dataType)) {
|
||||
return HttpUtil.downloadBytes(this.getValue());
|
||||
return HttpUtil.downloadBytes(value);
|
||||
}
|
||||
if (PictureProvideDataType.ABSOLUTE_PATH.equals(dataType)) {
|
||||
return FileUtil.readBytes(this.getValue());
|
||||
return FileUtil.readBytes(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.wmyun.farmwork.word.core.model.ext;
|
||||
|
||||
import com.wmyun.farmwork.word.core.model.AbstractExData;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @Date: 2025/3/7 16:52
|
||||
* @Created: by ZZSLL
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class TableExData extends AbstractExData {
|
||||
private List<Map<String, String>> value;
|
||||
}
|
@ -15,6 +15,8 @@ import lombok.Data;
|
||||
@AllArgsConstructor
|
||||
public class TextExData extends AbstractExData {
|
||||
|
||||
private String value;
|
||||
|
||||
private Boolean bold;
|
||||
|
||||
private Boolean italic;
|
||||
|
Loading…
Reference in New Issue
Block a user