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

发布于:2025-08-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

功能描述
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);
		//将和文档关联的资源填充到word
		for (ResourceInfo temp : listRes) {
			//这里的资源resId和书签名是同一个
			ResourceInfo res = resourceInfoService.getResourceById(temp.getResId());
			//resText是富文本内容
			OfficeUtils.updateBookmark(builder, res.getResId(), res.getResText());
		}
		//文档回传到minio
		ByteArrayOutputStream 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,用于特殊用途,处理插入富文本样式混轮的BUG
	private 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);
		}
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到