| 1 | package pro.verron.officestamper.core; | |
| 2 | ||
| 3 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
| 4 | import org.docx4j.openpackaging.exceptions.InvalidFormatException; | |
| 5 | import org.docx4j.openpackaging.packages.OpcPackage; | |
| 6 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
| 7 | import org.docx4j.openpackaging.parts.PartName; | |
| 8 | import org.docx4j.openpackaging.parts.Parts; | |
| 9 | import org.docx4j.openpackaging.parts.WordprocessingML.CommentsPart; | |
| 10 | import org.docx4j.wml.*; | |
| 11 | import org.docx4j.wml.R.CommentReference; | |
| 12 | import pro.verron.officestamper.api.Comment; | |
| 13 | import pro.verron.officestamper.api.DocxPart; | |
| 14 | import pro.verron.officestamper.api.OfficeStamperException; | |
| 15 | import pro.verron.officestamper.utils.wml.DocxIterator; | |
| 16 | ||
| 17 | import java.math.BigInteger; | |
| 18 | import java.util.*; | |
| 19 | ||
| 20 | import static java.util.function.Function.identity; | |
| 21 | import static java.util.stream.Collectors.toMap; | |
| 22 | ||
| 23 | /// Utility class for working with comments in a DOCX document. | |
| 24 | /// | |
| 25 | /// @author Joseph Verron | |
| 26 | /// @author Tom Hombergs | |
| 27 | /// @version ${version} | |
| 28 | /// @since 1.0.0 | |
| 29 | public class CommentUtil { | |
| 30 | private static final PartName WORD_COMMENTS_PART_NAME; | |
| 31 | ||
| 32 | static { | |
| 33 | try { | |
| 34 | WORD_COMMENTS_PART_NAME = new PartName("/word/comments.xml"); | |
| 35 | } catch (InvalidFormatException e) { | |
| 36 | throw new OfficeStamperException(e); | |
| 37 | } | |
| 38 | } | |
| 39 | ||
| 40 | private CommentUtil() { | |
| 41 | throw new OfficeStamperException("Utility class shouldn't be instantiated"); | |
| 42 | } | |
| 43 | ||
| 44 | /// Retrieves the comment associated with a given paragraph content within a WordprocessingMLPackage document. | |
| 45 | /// | |
| 46 | /// @param contentAccessor the content accessor to search for comments. | |
| 47 | /// @param document the WordprocessingMLPackage document containing the paragraph and its comments. | |
| 48 | /// @return a collection of found comments. | |
| 49 | public static Collection<Comments.Comment> getCommentFor(ContentAccessor contentAccessor, OpcPackage document) { | |
| 50 | var comments = getCommentsPart(document.getParts()).map(CommentUtil::extractContent) | |
| 51 | .map(Comments::getComment) | |
| 52 | .stream() | |
| 53 | .flatMap(Collection::stream) | |
| 54 | .toList(); | |
| 55 | ||
| 56 | var result = new ArrayList<Comments.Comment>(); | |
| 57 | var commentIterator = new DocxIterator(contentAccessor).selectClass(CommentRangeStart.class); | |
| 58 |
1
1. getCommentFor : negated conditional → NO_COVERAGE |
while (commentIterator.hasNext()) { |
| 59 | var crs = commentIterator.next(); | |
| 60 |
1
1. getCommentFor : removed call to java/util/Optional::ifPresent → NO_COVERAGE |
findCommentById(comments, crs.getId()).ifPresent(result::add); |
| 61 | } | |
| 62 |
1
1. getCommentFor : replaced return value with Collections.emptyList for pro/verron/officestamper/core/CommentUtil::getCommentFor → NO_COVERAGE |
return result; |
| 63 | } | |
| 64 | ||
| 65 | /// Retrieves the CommentsPart from the given Parts object. | |
| 66 | /// | |
| 67 | /// @param parts the Parts object containing the various parts of the document. | |
| 68 | /// | |
| 69 | /// @return an Optional containing the CommentsPart if found, or an empty Optional if not found. | |
| 70 | public static Optional<CommentsPart> getCommentsPart(Parts parts) { | |
| 71 |
1
1. getCommentsPart : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentUtil::getCommentsPart → KILLED |
return Optional.ofNullable((CommentsPart) parts.get(WORD_COMMENTS_PART_NAME)); |
| 72 | } | |
| 73 | ||
| 74 | /// Extracts the contents of a given [CommentsPart]. | |
| 75 | /// | |
| 76 | /// @param commentsPart the [CommentsPart] from which content will be extracted | |
| 77 | /// | |
| 78 | /// @return the [Comments] instance containing the content of the provided comments part | |
| 79 | /// | |
| 80 | /// @throws OfficeStamperException if an error occurs while retrieving the content | |
| 81 | public static Comments extractContent(CommentsPart commentsPart) { | |
| 82 | try { | |
| 83 |
1
1. extractContent : replaced return value with null for pro/verron/officestamper/core/CommentUtil::extractContent → KILLED |
return commentsPart.getContents(); |
| 84 | } catch (Docx4JException e) { | |
| 85 | throw new OfficeStamperException("Error while searching comment.", e); | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | private static Optional<Comments.Comment> findCommentById(List<Comments.Comment> comments, BigInteger id) { | |
| 90 | for (Comments.Comment comment : comments) { | |
| 91 |
1
1. findCommentById : negated conditional → NO_COVERAGE |
if (id.equals(comment.getId())) { |
| 92 |
1
1. findCommentById : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentUtil::findCommentById → NO_COVERAGE |
return Optional.of(comment); |
| 93 | } | |
| 94 | } | |
| 95 | return Optional.empty(); | |
| 96 | } | |
| 97 | ||
| 98 | /// Returns the string value of the specified comment object. | |
| 99 | /// | |
| 100 | /// @param comment a [Comment] object | |
| 101 | public static void deleteComment(Comment comment) { | |
| 102 | CommentRangeEnd end = comment.getCommentRangeEnd(); | |
| 103 | ContentAccessor endParent = (ContentAccessor) end.getParent(); | |
| 104 | endParent.getContent() | |
| 105 | .remove(end); | |
| 106 | CommentRangeStart start = comment.getCommentRangeStart(); | |
| 107 | var parent = start.getParent(); | |
| 108 | ContentAccessor startParent = (ContentAccessor) parent; | |
| 109 | startParent.getContent() | |
| 110 | .remove(start); | |
| 111 |
1
1. deleteComment : negated conditional → KILLED |
if (startParent instanceof CTSmartTagRun tag && tag.getContent() |
| 112 |
1
1. deleteComment : negated conditional → KILLED |
.isEmpty()) ((ContentAccessor) tag.getParent()).getContent() |
| 113 | .remove(tag); | |
| 114 | CommentReference reference = comment.getCommentReference(); | |
| 115 |
1
1. deleteComment : negated conditional → KILLED |
if (reference != null) { |
| 116 | ContentAccessor referenceParent = (ContentAccessor) reference.getParent(); | |
| 117 | referenceParent.getContent() | |
| 118 | .remove(reference); | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | /// Creates a [Comment] object. | |
| 123 | /// | |
| 124 | /// @param docxPart the document part. | |
| 125 | /// @param crs the comment range start. | |
| 126 | /// @param document the document. | |
| 127 | /// @param contentAccessor the content accessor. | |
| 128 | /// | |
| 129 | /// @return the comment. | |
| 130 | public static Comment comment( | |
| 131 | DocxPart docxPart, | |
| 132 | CommentRangeStart crs, | |
| 133 | WordprocessingMLPackage document, | |
| 134 | ContentAccessor contentAccessor | |
| 135 | ) { | |
| 136 | var iterator = new DocxIterator(contentAccessor).slice(crs, null); | |
| 137 | CommentRangeEnd cre = null; | |
| 138 | CommentReference cr = null; | |
| 139 | var commentId = crs.getId(); | |
| 140 |
3
1. comment : negated conditional → TIMED_OUT 2. comment : negated conditional → KILLED 3. comment : negated conditional → KILLED |
while (iterator.hasNext() && (cr == null || cre == null)) { |
| 141 | var element = iterator.next(); | |
| 142 |
3
1. comment : negated conditional → KILLED 2. comment : negated conditional → KILLED 3. comment : negated conditional → KILLED |
if (element instanceof CommentRangeEnd found && cre == null && Objects.equals(found.getId(), commentId)) { |
| 143 | cre = found; | |
| 144 | } | |
| 145 |
3
1. comment : negated conditional → KILLED 2. comment : negated conditional → KILLED 3. comment : negated conditional → KILLED |
else if (element instanceof CommentReference found && cr == null && Objects.equals(found.getId(), |
| 146 | commentId)) { | |
| 147 | cr = found; | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 |
1
1. comment : negated conditional → KILLED |
if (cre == null) throw new IllegalStateException("Could not find comment range end or reference"); |
| 152 | ||
| 153 | var comment = comment(document, commentId); | |
| 154 | ||
| 155 | ||
| 156 |
1
1. comment : replaced return value with null for pro/verron/officestamper/core/CommentUtil::comment → KILLED |
return new StandardComment(docxPart, (CTSmartTagRun) crs.getParent(), crs, cre, comment, cr); |
| 157 | } | |
| 158 | ||
| 159 | private static Comments.Comment comment(WordprocessingMLPackage document, BigInteger commentId) { | |
| 160 |
1
1. comment : replaced return value with null for pro/verron/officestamper/core/CommentUtil::comment → KILLED |
return getCommentsPart(document.getParts()).map(CommentUtil::extractContent) |
| 161 | .map(Comments::getComment) | |
| 162 | .stream() | |
| 163 | .flatMap(Collection::stream) | |
| 164 | .collect(toMap(Comments.Comment::getId, identity())) | |
| 165 | .get(commentId); | |
| 166 | } | |
| 167 | } | |
Mutations | ||
| 58 |
1.1 |
|
| 60 |
1.1 |
|
| 62 |
1.1 |
|
| 71 |
1.1 |
|
| 83 |
1.1 |
|
| 91 |
1.1 |
|
| 92 |
1.1 |
|
| 111 |
1.1 |
|
| 112 |
1.1 |
|
| 115 |
1.1 |
|
| 140 |
1.1 2.2 3.3 |
|
| 142 |
1.1 2.2 3.3 |
|
| 145 |
1.1 2.2 3.3 |
|
| 151 |
1.1 |
|
| 156 |
1.1 |
|
| 160 |
1.1 |