From 2f8a2aafba00e42c244107071b9e390549e389b2 Mon Sep 17 00:00:00 2001 From: zzs Date: Tue, 4 Mar 2025 11:28:23 +0800 Subject: [PATCH] feat: bookmark replace --- .../farmwork/word/core/BookmarkExec.java | 118 +++++++++++------- .../core/enums/PictureProvideNameType.java | 22 ++++ .../word/core/model/AbstractExData.java | 15 ++- .../word/core/model/ext/PictureExData.java | 29 +++++ 4 files changed, 135 insertions(+), 49 deletions(-) create mode 100644 wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/enums/PictureProvideNameType.java 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 2ef5db1..8859c5a 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 @@ -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 data, byte[] word, File file) { - Map map = data.stream().collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b)); + Map 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 runs = paragraph.getRuns(); List bookmarkStartList = paragraph.getCTP().getBookmarkStartList(); for (CTBookmark bookmark : bookmarkStartList) { String name = bookmark.getName(); - if (map.containsKey(name)) { - List 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 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); } } diff --git a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/enums/PictureProvideNameType.java b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/enums/PictureProvideNameType.java new file mode 100644 index 0000000..0559468 --- /dev/null +++ b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/enums/PictureProvideNameType.java @@ -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; +} diff --git a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/AbstractExData.java b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/AbstractExData.java index 13877b1..e5eec79 100644 --- a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/AbstractExData.java +++ b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/AbstractExData.java @@ -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; } diff --git a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/ext/PictureExData.java b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/ext/PictureExData.java index 4230d7a..d4b68a3 100644 --- a/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/ext/PictureExData.java +++ b/wmyun-framework/wmyun-spring-boot-starter-word/src/main/java/com/wmyun/farmwork/word/core/model/ext/PictureExData.java @@ -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); + }; + } }