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;\"/>";
设计思路(不可行)
一开始我的思路是下面这样的,结果导致无法成功,如果你也是下面的这种思路,你可以借鉴一下。看是不是这样的思路,提前换思路
书签始终不删除,只是清空书签内容,然后从新插入内容(无法满足复杂的富文本,例如表格)
- 获取文档
- 光标移动到指定书签位置处
- 清空标签内容
- 插入富文本
- 保存文档
理论上:开始书签->富文本表格->结束书签,但是实际上得到的是:开始书签->结束书签->富文本表格。最后会导致,书签没有包裹住富文本,最后当多次更新以后,无法删除以前的富文本表格,导致插入很多表格。
例如:下面的a1标签,点击定位时,发现不是定位表格,而是表格后面的光标,因为对于二次编辑就没办法进行定位了
这种设计方式,只能用于文档的第一次创建,没办法实现多次更新。
而对于普通的文本,则是正常的。点击定位能够找到文本(因此可以正常执行后续的更新操作)
对于下面这种,手动创建:开始书签->富文本表格->结束书签的方式,最后打开word文档,会发现,这个书签没有加进去,也就是没有办法执行后面的二次更新。
builder.startBookmark(tag);
builder.insertHtml(tableHtml);
builder.endBookmark(tag);
设计思路(可行)
下面的这个思路可行。但是留有一些小细节,但是不影响大体功能
- 获取文档
- 光标移动到指定书签位置处
- 删除书签的内容
- 删除书签
- 创建开始书签(和删除的书签保持同一个名)
- 创建临时文档
- 将富文本写入临时文档
- 将临时文档追加到我们目标文档
- 创建结束标签
- 清空多余的空行
- 保存文档
核心逻辑
下面的代码中,在插入文档之前,执行了一个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);}}
}