当前位置: 首页 > news >正文

aspose word for java 使用书签进行内容填充和更新

功能描述
1.用户通过在线/离线操作的方式对模板word进行插入书签
2.获取书签对应的内容(表格、图片、段落、普通文本)等内容插入word文档中
3.后续相关对应的内容发生变化时(例如下面的html假设为动态生成的),需要同步更新到word中。
在这里插入图片描述
最大的难点在于,当上面的填充的内容,如果发生变化时,需要更新word。此时就需要定位到书签的位置,然后先删除之前填充的,在获取最新的html资源,然后再次填充(这里我折腾了接近2天。)

下面是我的伪代码示例

public void generateReportWithBankmark(String documentId, List<ResourceInfo> listRes) throws Exception {//从minio获取文档InputStream fileStream = minioService.getFileStream(documentId);Document docx = new Document(fileStream);DocumentBuilder builder = new DocumentBuilder(docx);//将和文档关联的资源填充到wordfor (ResourceInfo temp : listRes) {//这里的资源resId和书签名是同一个ResourceInfo res = resourceInfoService.getResourceById(temp.getResId());//resText是富文本内容OfficeUtils.updateBookmark(builder, res.getResId(), res.getResText());}//文档回传到minioByteArrayOutputStream outputStream = new ByteArrayOutputStream();docx.save(outputStream, SaveFormat.DOCX);ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());long fileSize = outputStream.size();minioService.deleteFile(documentId);minioService.uploadFile(inputStream, documentId, Constant.WORD, fileSize);}

准备工作

准备带标签的文档

在这里插入图片描述

准备3个富文本

分别为表格、段落、图片

String html1 = "<table border='1' cellpadding='4'>" +"  <tr><th>姓名</th><th>年龄</th><th>职位</th></tr>" +"  <tr><td>张三</td><td>30</td><td>工程师</td></tr>" +"  <tr><td>李四</td><td>28</td><td>设计师</td></tr>" +"</table>";
String html2 = "<div style=\"font-family: Arial, sans-serif; font-size: 11pt; line-height: 1.5;\">"+ "  <p>1. 用户通过在线/离线操作的方式对模板Word进行插入书签</p>"+ "  <p>2. 获取书签对应的内容(表格、图片、段落、普通文本)等内容插入Word文档中</p>"+ "  <p>3. 后续相关对应的内容发生变化时,需要同步更新到Word中。</p>"+ "</div>";
String html3 = "<img src=\"https://ww2.sinaimg.cn/mw690/007ut4Uhly1hx4v37mpxcj30u017cgrv.jpg\" alt=\"图片内容\" style=\"max-width: 100%; height: auto;\"/>";

设计思路(不可行)

一开始我的思路是下面这样的,结果导致无法成功,如果你也是下面的这种思路,你可以借鉴一下。看是不是这样的思路,提前换思路
书签始终不删除,只是清空书签内容,然后从新插入内容(无法满足复杂的富文本,例如表格)

  1. 获取文档
  2. 光标移动到指定书签位置处
  3. 清空标签内容
  4. 插入富文本
  5. 保存文档

理论上:开始书签->富文本表格->结束书签,但是实际上得到的是:开始书签->结束书签->富文本表格。最后会导致,书签没有包裹住富文本,最后当多次更新以后,无法删除以前的富文本表格,导致插入很多表格。
例如:下面的a1标签,点击定位时,发现不是定位表格,而是表格后面的光标,因为对于二次编辑就没办法进行定位了
这种设计方式,只能用于文档的第一次创建,没办法实现多次更新。
在这里插入图片描述
而对于普通的文本,则是正常的。点击定位能够找到文本(因此可以正常执行后续的更新操作)
在这里插入图片描述

对于下面这种,手动创建:开始书签->富文本表格->结束书签的方式,最后打开word文档,会发现,这个书签没有加进去,也就是没有办法执行后面的二次更新。
builder.startBookmark(tag);
builder.insertHtml(tableHtml);
builder.endBookmark(tag);

设计思路(可行)

下面的这个思路可行。但是留有一些小细节,但是不影响大体功能

  1. 获取文档
  2. 光标移动到指定书签位置处
  3. 删除书签的内容
  4. 删除书签
  5. 创建开始书签(和删除的书签保持同一个名)
  6. 创建临时文档
  7. 将富文本写入临时文档
  8. 将临时文档追加到我们目标文档
  9. 创建结束标签
  10. 清空多余的空行
  11. 保存文档

核心逻辑

下面的代码中,在插入文档之前,执行了一个builder.insertHtml(MARK);然后再文档书签更新完毕以后,又把MARK替换为空的原因是:
在word编辑中,当我们对某段文本设置了某个样式(例如黑体),然后接着把这段文本挨个删除以后,但是没有删除整段文本,接着在继续输入文本,会发现,输入的文本是黑体。也就是word中,文本是清除了,但是文本的样式却保留了。但是这里我们是不可以删除整段文本的,因为我们已经把书签删了,此时光标的位置替代了书签的位置,把整段文本删除,就会导致光标失去定位了,所以可以在这段文本的前面,从新插入一个新的文本标识,在新的文本后面插入新的富文本,这样新的富文本,就不会复用以前的富文本的样式了。最后我们把新插入的文本标识清空即可。

	private static final String MARK = "3b069b88-8c7a-43af-a75c-45885a78f69e";public static void updateBookmark(DocumentBuilder builder, String tag, String html) {Bookmark bookmark = builder.getDocument().getRange().getBookmarks().get(tag);if(bookmark != null) {try {//1清空书签内容并删除书签、2创建临时文档、3创建同名书签、4插入临时文档builder.moveToBookmark(tag);bookmark.setText("");bookmark.remove();builder.startBookmark(tag);Document tmp = new Document();DocumentBuilder tb = new DocumentBuilder(tmp);tb.insertHtml(html);//注销下面这行代码,会导致builder.insertDocument插入和原来的样式产生混乱(尤其表格),因此从新插入一个富文本,就不会混乱,最后将生成的MARK替换为空builder.insertHtml(MARK);//builder.insertHtml(html); 改代码插入复杂富文本会导致标签失效,例如表格富文本,因此采用插入临时文档builder.insertDocument(tmp, ImportFormatMode.USE_DESTINATION_STYLES);builder.endBookmark(tag);builder.getDocument().getRange().replace(MARK, "");} catch (Exception e) {log.info("更新书签内容失败,失败原因:",e);}}}

完整代码

import com.aspose.words.Bookmark;
import com.aspose.words.Document;
import com.aspose.words.DocumentBuilder;
import com.aspose.words.ImportFormatMode;
import com.aspose.words.Node;
import com.aspose.words.NodeCollection;
import com.aspose.words.NodeType;
import com.aspose.words.Paragraph;import lombok.extern.log4j.Log4j2;@Log4j2
public class OfficeUtils {//一个随机的UUID,用于特殊用途,处理插入富文本样式混轮的BUGprivate static final String MARK = "3b069b88-8c7a-43af-a75c-45885a78f69e";/** @Description: 更新书签中的内容* @author: 胡涛* @mail: hutao_2017@aliyun.com* @date: 2025年8月12日 下午1:50:47*/public static void updateBookmark(DocumentBuilder builder, String tag, String html) {Bookmark bookmark = builder.getDocument().getRange().getBookmarks().get(tag);if(bookmark != null) {try {//1清空书签内容并删除书签、2创建临时文档、3创建同名书签、4插入临时文档builder.moveToBookmark(tag);bookmark.setText("");bookmark.remove();builder.startBookmark(tag);Document tmp = new Document();DocumentBuilder tb = new DocumentBuilder(tmp);tb.insertHtml(html);//注销下面这行代码,会导致builder.insertDocument插入和原来的样式产生混乱(尤其表格),因此从新插入一个富文本,就不会混乱,最后将生成的MARK替换为空builder.insertHtml(MARK);//builder.insertHtml(html); 改代码插入复杂富文本会导致标签失效,例如表格富文本,因此采用插入临时文档builder.insertDocument(tmp, ImportFormatMode.USE_DESTINATION_STYLES);builder.endBookmark(tag);builder.getDocument().getRange().replace(MARK, "");} catch (Exception e) {log.info("更新书签内容失败,失败原因:",e);}}}/** @Description: 移除空白行* @author: 胡涛* @mail: hutao_2017@aliyun.com* @date: 2025年8月12日 下午2:15:49*/public static void removeBlank(Document docx) {NodeCollection<?> paragraphs = docx.getChildNodes(NodeType.PARAGRAPH, true);for (int i = paragraphs.getCount() - 1; i >= 0; i--) {Node node = paragraphs.get(i);if(node instanceof Paragraph) {Paragraph para = (Paragraph)node;if (para.getText().trim().isEmpty() && !isSpecialContent(para)) {para.remove();}}}}/** @Description: 是否包含特殊内容(书签、占位符、域)* @author: 胡涛* @mail: hutao_2017@aliyun.com* @date: 2025年8月12日 下午2:10:11*/private static boolean isSpecialContent(Paragraph para) {//书签@SuppressWarnings("unchecked")NodeCollection<Node> bookmarkStarts = para.getChildNodes(NodeType.BOOKMARK_START, true);@SuppressWarnings("unchecked")NodeCollection<Node> bookmarkEnds = para.getChildNodes(NodeType.BOOKMARK_END, true);if (bookmarkStarts.getCount() > 0 || bookmarkEnds.getCount() > 0) {return true;}// 占位符String text = para.getText();if (text.contains("${") && text.contains("}") || text.contains("{{") && text.contains("}}")) {return true;}// 域if (para.getRange().getFields().getCount() > 0) {return true;}return false;}public static void main(String[] args) throws Exception {// 示例用法String path1 = "C:\\Users\\胡涛\\Desktop\\test.docx";String path2 = "C:\\Users\\胡涛\\Desktop\\test2.docx";String tag1 = "a1";String tag2 = "a2";String tag3 = "a3";for (int i = 0; i < 1; i++) {System.out.println("使用最初的模板test文档创建test2文档");String html1 = "<table border='1' cellpadding='4'>" +"  <tr><th>姓名</th><th>年龄</th><th>职位</th></tr>" +"  <tr><td>张三</td><td>30</td><td>工程师</td></tr>" +"  <tr><td>李四</td><td>28</td><td>设计师</td></tr>" +"</table>";String html2 = "<div style=\"font-family: Arial, sans-serif; font-size: 11pt; line-height: 1.5;\">"+ "  <p>1. 用户通过在线/离线操作的方式对模板Word进行插入书签</p>"+ "  <p>2. 获取书签对应的内容(表格、图片、段落、普通文本)等内容插入Word文档中</p>"+ "  <p>3. 后续相关对应的内容发生变化时,需要同步更新到Word中。</p>"+ "</div>";String html3 = "<img src=\"https://ww2.sinaimg.cn/mw690/007ut4Uhly1hx4v37mpxcj30u017cgrv.jpg\" alt=\"图片内容\" style=\"max-width: 100%; height: auto;\"/>";Document docx = new Document(path1);DocumentBuilder builder = new DocumentBuilder(docx);updateBookmark(builder,tag1, html1);updateBookmark(builder,tag2, html2);updateBookmark(builder,tag3, html3);docx.save(path2);}for (int i = 1; i < 5; i++) {System.out.println("模拟第"+ i+"次更新文档中的标签");String html1 = "<table border='1' cellpadding='4'>" +"  <tr><th>姓名</th><th>年龄</th><th>职位</th></tr>" +"  <tr><td>张三</td><td>30</td><td>工程师</td></tr>" +"  <tr><td>李四</td><td>28</td><td>设计师</td></tr>" +"</table>";String html2 = "<div style=\"font-family: Arial, sans-serif; font-size: 11pt; line-height: 1.5;\">"+ "  <p>1. 用户通过在线/离线操作的方式对模板Word进行插入书签</p>"+ "  <p>2. 获取书签对应的内容(表格、图片、段落、普通文本)等内容插入Word文档中</p>"+ "  <p>3. 后续相关对应的内容发生变化时,需要同步更新到Word中。</p>"+ "</div>";String html3 = "<img src=\"https://bpic.wotucdn.com/original/33/51/83/33518300-1d89270902df00d01dedec878ee357ab.jpeg!/quality/91/unsharp/true/compress/true/watermark/url/bG9nby53YXRlci52MTAucG5n/repeat/true/rotate/auto/fw/320/clip/320x556a0a0\" alt=\"图片内容\" style=\"max-width: 100%; height: auto;\"/>";Document docx = new Document(path2);DocumentBuilder builder = new DocumentBuilder(docx);updateBookmark(builder,tag1, html1);updateBookmark(builder,tag2, html2);updateBookmark(builder,tag3, html3);removeBlank(docx);docx.save(path2);}}
}
http://www.xdnf.cn/news/1288243.html

相关文章:

  • SM4对称加密算法的加密模式介绍
  • Python Day28 HTML 与 CSS 核心知识点 及例题分析
  • 自动驾驶 HIL 测试:构建 “以假乱真” 的实时数据注入系统
  • 《嵌入式Linux应用编程(四):Linux文件IO系统调用深度解析》
  • GraphQL 原理、应用与实践指南
  • 【Altium designer】快速建立原理图工程的步骤
  • Day05 店铺营业状态设置 Redis
  • MySQL-多表查询
  • 第23章,景深:技术综述
  • 下一代防火墙技术
  • 【KO】android 面试 算法
  • 数字气压传感器,筑牢汽车TPMS胎压监测系统的精准感知基石
  • 西门子S7-200与S7-1200通过PPI以太网模块通讯,赋能汽车制造行业发展
  • 如何在 Ubuntu 24.04 LTS Linux 中安装 JSON Server
  • WebAssembly的原理与使用
  • 前端最新Vue2+Vue3基础入门到实战项目全套教程,自学前端vue就选黑马程序员,一套全通关!笔记
  • Tauri Qt孰优孰劣
  • 计算机毕设不知道选什么题目?基于Spark的糖尿病数据分析系统【Hadoop+Spark+python】
  • 数据结构 二叉树(2)堆
  • 91、23种经典设计模式
  • AI大模型基础:BERT、GPT、Vision Transformer(ViT)的原理、实现与应用
  • 农业智慧大屏系统 - Flask + Vue实现
  • 飞算AI:企业智能化转型的新引擎
  • 嵌入式硬件——ARM
  • 【虚拟机】VMwareWorkstation17Pro安装步骤
  • 三维工厂设计软件 AutoCAD Plant 3D 安装图文教程
  • Nginx 启用 HTTPS:阿里云免费 SSL 证书详细图文教程(新手0.5小时可完成)
  • C# 基于halcon的视觉工作流-章29-边缘提取-亚像素
  • AI Agent——基于 LangGraph 的多智能体任务路由与执行系统实战
  • 蓝桥杯电子赛----嵌入式赛道备赛LED