feat: bookmark replace

This commit is contained in:
zzs 2025-03-04 11:28:23 +08:00
parent 431a467c8a
commit 2f8a2aafba
4 changed files with 135 additions and 49 deletions

View File

@ -4,6 +4,7 @@ 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.enums.PictureProvideNameType;
import com.wmyun.farmwork.word.core.model.AbstractExData;
import com.wmyun.farmwork.word.core.model.BookmarkReplaceDataModel;
import com.wmyun.farmwork.word.core.model.ext.PictureExData;
@ -35,7 +36,8 @@ public class BookmarkExec {
public final static String tmpDir = System.getProperty("java.io.tmpdir") + File.separator;
public static File replace(List<BookmarkReplaceDataModel> data, byte[] word, File file) {
Map<String, BookmarkReplaceDataModel> map = data.stream().collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
.collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) {
// 收集所有段落
@ -76,62 +78,82 @@ public class BookmarkExec {
}
for (XWPFParagraph paragraph : allParagraphs) {
List<XWPFRun> runs = paragraph.getRuns();
List<CTBookmark> bookmarkStartList = paragraph.getCTP().getBookmarkStartList();
for (CTBookmark bookmark : bookmarkStartList) {
String name = bookmark.getName();
if (map.containsKey(name)) {
List<XWPFRun> runs = paragraph.getRuns();
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 (!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);
}
}
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(runs.size() - 1);
run = runs.get(idx);
}
BookmarkReplaceDataModel model = map.get(name);
BookmarkType type = model.getType();
// 处理文本
if (BookmarkType.TEXT.equals(type)) {
run.setText(model.getValue());
} else if (!runs.isEmpty()){
// 处理一段末尾的书签一般不存在这种情况
run = runs.get(runs.size() - 1);
} else {
run = paragraph.createRun();
}
if (run == null) {
continue;
}
BookmarkReplaceDataModel model = dataMap.get(name);
BookmarkType type = model.getType();
// 处理文本
if (BookmarkType.TEXT.equals(type)) {
run.setText(model.getValue());
}
// 处理图片以嵌入式插入图片
if (BookmarkType.PICTURE.equals(type)) {
AbstractExData extData = model.getExtData();
if (extData instanceof PictureExData picture && PictureProvideNameType.BOOKMARK.equals(picture.getNameType())) {
try {
run.addPicture(new ByteArrayInputStream(picture.readAsByteArray()), XWPFDocument.PICTURE_TYPE_PNG, picture.getPictureName(), Units.toEMU(picture.getWidth()), Units.toEMU(picture.getHeight()));
} catch (InvalidFormatException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
// 处理图片以嵌入式插入图片
if (BookmarkType.PICTURE.equals(type)) {
AbstractExData extData = model.getExtData();
if (extData instanceof PictureExData picture) {
try {
run.addPicture(new ByteArrayInputStream(picture.readAsByteArray()), XWPFDocument.PICTURE_TYPE_PNG, picture.getPictureName(), Units.toEMU(picture.getWidth()), Units.toEMU(picture.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);
}
}
}
@ -147,7 +169,7 @@ public class BookmarkExec {
doc.write(out);
}
return outFile;
} catch (IOException e) {
} catch (IOException | InvalidFormatException e) {
throw new RuntimeException(e);
}
}

View File

@ -0,0 +1,22 @@
package com.wmyun.farmwork.word.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @Classname PictureProvideNameType
* @Description TODO
* @Date 2025/3/4 10:06
* @Created by violet
*/
@AllArgsConstructor
@Getter
public enum PictureProvideNameType {
BOOKMARK("BOOKMARK"),
DESCRIPTOR("DESCRIPTOR");
private final String type;
}

View File

@ -1,5 +1,10 @@
package com.wmyun.farmwork.word.core.model;
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.PictureExData;
import com.wmyun.farmwork.word.core.model.ext.TextExData;
import lombok.Data;
/**
@ -9,6 +14,14 @@ import lombok.Data;
*/
@Data
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = TextExData.class, name = "TEXT"),
@JsonSubTypes.Type(value = PictureExData.class, name = "PICTURE")
})
public class AbstractExData {
private BookmarkType type;
}

View File

@ -3,10 +3,12 @@ package com.wmyun.farmwork.word.core.model.ext;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import com.wmyun.farmwork.word.core.enums.PictureProvideDataType;
import com.wmyun.farmwork.word.core.enums.PictureProvideNameType;
import com.wmyun.farmwork.word.core.model.AbstractExData;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.poi.xwpf.usermodel.Document;
import java.net.URL;
import java.util.Base64;
@ -28,6 +30,8 @@ public class PictureExData extends AbstractExData {
private PictureProvideDataType dataType;
private PictureProvideNameType nameType;
private String data;
@SneakyThrows
@ -43,4 +47,29 @@ public class PictureExData extends AbstractExData {
}
return null;
}
public int readPictureType() {
// 获取文件名假设 pictureName 已经赋值
String name = this.pictureName;
if (name == null || !name.contains(".")) {
throw new IllegalArgumentException("无效的图片名称:" + name);
}
// 获取扩展名转换为小写
String ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase();
// 根据扩展名返回对应的类型ID
return switch (ext) {
case "emf" -> Document.PICTURE_TYPE_EMF;
case "wmf" -> Document.PICTURE_TYPE_WMF;
case "pict" -> Document.PICTURE_TYPE_PICT;
case "jpeg", "jpg" -> Document.PICTURE_TYPE_JPEG;
case "png" -> Document.PICTURE_TYPE_PNG;
case "dib" -> Document.PICTURE_TYPE_DIB;
case "gif" -> Document.PICTURE_TYPE_GIF;
case "tiff", "tif" -> Document.PICTURE_TYPE_TIFF;
case "eps" -> Document.PICTURE_TYPE_EPS;
case "bmp" -> Document.PICTURE_TYPE_BMP;
case "wpg" -> Document.PICTURE_TYPE_WPG;
default -> throw new IllegalArgumentException("不支持的图片类型: " + ext);
};
}
}