Hello!
We are experiencing an issue when working with comment ranges using Aspose.Words for Java. Specifically, we have a scenario where comment markers (CommentRangeStart and CommentRangeEnd) are placed across multiple paragraphs.
The structure after parsing our HTML input looks like this:
HTML Input:
<ul>
<li>{{data-comment-start="019777a3-5104-71a8-985f-04e99e557fcc"}}11111</li>
<li>2222{{data-comment-end="019777a3-5104-71a8-985f-04e99e557fcc"}}</li>
<li>3333</li>
</ul>
Paragraph 1 run 1:
{{data-comment-start="019777a3-5104-71a8-985f-04e99e557fcc"}}11111
Paragraph 2 run 1:
2222{{data-comment-end="019777a3-5104-71a8-985f-04e99e557fcc"}}
Paragraph 3 run 1:
3333
The issue is that when the comment range starts in one paragraph and ends in another, the Aspose comment does not highlight the intended range properly. It appears the CommentRangeEnd marker is not recognized correctly, resulting in the comment range ending prematurely or being improperly associated.
Could you please provide guidance on how to correctly implement comment ranges that span across multiple paragraphs?
Is there an official recommendation or best practice to ensure CommentRangeStart and CommentRangeEnd markers correctly highlight content across paragraph boundaries?
Thank you very much for your assistance.
Below is a snippet of code that works well, even with overlapping fragments—but only within a single paragraph.
public static void insertAsposeCommentsFromMarkers(
DocumentBuilder builder,
Map<UUID, Discussion> discussionMap
) {
com.aspose.words.Document asposeDoc = builder.getDocument();
Pattern markerPattern = Pattern.compile(
"\\{\\{data-comment-(start|end)=\"([^\"]+)\"\\}\\}"
);
for (com.aspose.words.Node node : asposeDoc.getChildNodes(NodeType.PARAGRAPH, true).toArray()) {
Paragraph paragraph = (Paragraph) node;
Map<UUID, Comment> commentMap = new HashMap<>();
Map<UUID, CommentRangeStart> openRanges = new HashMap<>();
List<Run> runs = new ArrayList<>(Arrays.asList(paragraph.getRuns().toArray()));
if (runs.isEmpty()) continue;
for (Run run : new ArrayList<>(runs)) {
String runText = run.getText();
if (runText == null) continue;
Matcher matcher = markerPattern.matcher(runText);
List<Marker> markers = new ArrayList<>();
while (matcher.find()) {
String type = matcher.group(1);
String uuidStr = matcher.group(2);
try {
UUID discussionUuid = UUID.fromString(uuidStr);
markers.add(new Marker(type, discussionUuid, matcher.start(), matcher.end()));
} catch (IllegalArgumentException e) {
log.error("Invalid UUID: {}", uuidStr);
}
}
int lastPos = 0;
for (Marker marker : markers) {
if (marker.start > lastPos) {
Run textRun = (Run) run.deepClone(false);
textRun.setText(runText.substring(lastPos, marker.start));
paragraph.insertBefore(textRun, run);
}
if ("start".equals(marker.type)) {
Comment comment = commentMap
.computeIfAbsent(marker.uuid, u -> createAsposeCommentByParagraph(paragraph, asposeDoc, discussionMap, u));
if (comment != null) {
CommentRangeStart start = new CommentRangeStart(asposeDoc, comment.getId());
paragraph.insertBefore(start, run);
openRanges.put(marker.uuid, start);
} else {
log.error("Aspose comment for discussion UUID: {} is null on start", marker.uuid);
}
} else {
if (openRanges.containsKey(marker.uuid)) {
Comment comment = commentMap.get(marker.uuid);
if (comment != null) {
CommentRangeEnd endMarker = new CommentRangeEnd(asposeDoc, comment.getId());
paragraph.insertBefore(endMarker, run);
openRanges.remove(marker.uuid);
log.debug("Added CommentRangeEnd for discussion UUID: {}", marker.uuid);
} else {
log.error("Aspose comment for discussion UUID: {} is null on end", marker.uuid);
}
} else {
log.error("No comment found for CommentRangeEnd with discussion UUID: {}", marker.uuid);
}
}
lastPos = marker.end;
}
if (lastPos < runText.length()) {
Run textRun = (Run) run.deepClone(false);
textRun.setText(runText.substring(lastPos));
paragraph.insertBefore(textRun, run);
}
run.remove();
}
}
}
private static Comment createAsposeCommentByParagraph(
com.aspose.words.Paragraph paragraph,
com.aspose.words.Document doc,
Map<UUID, Discussion> discussionMap,
UUID uuid
) {
Discussion disc = discussionMap.get(uuid);
if (disc == null || disc.getComments().isEmpty()) {
return null;
}
ru.vtb.dc.agreement.model.discussion.Comment first = disc.getComments().get(0);
Comment comment = new Comment(doc, first.getCreatedBy(), first.getCreatedBy(), fromLocalDateTime(first.getCreatedAt()));
comment.setText(first.getBody());
paragraph.appendChild(comment);
disc.getComments().stream().skip(1).forEach(reply -> {
try {
comment.addReply(reply.getCreatedBy(),
reply.getCreatedBy(),
fromLocalDateTime(reply.getCreatedAt()),
reply.getBody());
} catch (Exception e) {
log.error("Error while creating comment replies for discussion UUID: {}", uuid);
}
});
return comment;
}
private Date fromLocalDateTime(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
private static class Marker {
String type;
UUID uuid;
int start;
int end;
Marker(String type, UUID uuid, int start, int end) {
this.type = type;
this.uuid = uuid;
this.start = start;
this.end = end;
}
}
}