| 1 | package pro.verron.officestamper.core; | |
| 2 | ||
| 3 | import org.docx4j.wml.*; | |
| 4 | import org.jvnet.jaxb2_commons.ppp.Child; | |
| 5 | import pro.verron.officestamper.api.*; | |
| 6 | import pro.verron.officestamper.utils.wml.DocxIterator; | |
| 7 | import pro.verron.officestamper.utils.wml.WmlUtils; | |
| 8 | ||
| 9 | import java.util.Collection; | |
| 10 | import java.util.List; | |
| 11 | import java.util.Optional; | |
| 12 | import java.util.function.Consumer; | |
| 13 | ||
| 14 | import static java.util.stream.Collectors.joining; | |
| 15 | import static pro.verron.officestamper.api.OfficeStamperException.throwing; | |
| 16 | import static pro.verron.officestamper.utils.wml.WmlUtils.getFirstParentWithClass; | |
| 17 | import static pro.verron.officestamper.utils.wml.WmlUtils.isTagElement; | |
| 18 | ||
| 19 | /// Represents a wrapper for managing and manipulating DOCX paragraph elements. This class provides methods to | |
| 20 | /// manipulate the underlying paragraph content, process placeholders, and interact with runs within the paragraph. | |
| 21 | public class StandardParagraph | |
| 22 | implements Paragraph { | |
| 23 | ||
| 24 | private final DocxPart part; | |
| 25 | private final ContentAccessor contents; | |
| 26 | private final ArrayListWml<Object> p; | |
| 27 | ||
| 28 | /// Constructs a new instance of the StandardParagraph class. | |
| 29 | /// | |
| 30 | /// @param part the source DocxPart that contains the paragraph content. | |
| 31 | /// @param paragraphContent the list of objects representing the paragraph content. | |
| 32 | /// @param p the P object representing the paragraph's structure. | |
| 33 | private StandardParagraph(DocxPart part, ContentAccessor paragraphContent, ArrayListWml<Object> p) { | |
| 34 | this.part = part; | |
| 35 | this.contents = paragraphContent; | |
| 36 | this.p = p; | |
| 37 | } | |
| 38 | ||
| 39 | /// Creates a new instance of `StandardParagraph` from the provided [DocxPart] and parent object. | |
| 40 | /// | |
| 41 | /// @param part the source DocxPart. | |
| 42 | /// @param parent the parent object. | |
| 43 | /// | |
| 44 | /// @return a new StandardParagraph instance. | |
| 45 | public static StandardParagraph from(DocxPart part, Object parent) { | |
| 46 |
1
1. from : replaced return value with null for pro/verron/officestamper/core/StandardParagraph::from → TIMED_OUT |
return switch (parent) { |
| 47 | case P p -> from(part, p); | |
| 48 | case CTSdtContentRun contentRun -> from(part, contentRun); | |
| 49 |
1
1. from : negated conditional → NO_COVERAGE |
case CTSmartTagRun smartTagRun when isTagElement(smartTagRun, "officestamper") -> |
| 50 | from(part, smartTagRun.getParent()); | |
| 51 | default -> throw new OfficeStamperException("Unsupported parent type: " + parent.getClass()); | |
| 52 | }; | |
| 53 | } | |
| 54 | ||
| 55 | /// Creates a new instance of StandardParagraph using the provided DocxPart and P objects. | |
| 56 | /// | |
| 57 | /// @param source the source DocxPart containing the paragraph. | |
| 58 | /// @param paragraph the P object representing the structure and content of the paragraph. | |
| 59 | /// | |
| 60 | /// @return a new instance of StandardParagraph constructed based on the provided source and paragraph. | |
| 61 | public static StandardParagraph from(DocxPart source, P paragraph) { | |
| 62 |
1
1. from : replaced return value with null for pro/verron/officestamper/core/StandardParagraph::from → KILLED |
return new StandardParagraph(source, paragraph, (ArrayListWml<Object>) paragraph.getContent()); |
| 63 | } | |
| 64 | ||
| 65 | /// Creates a new instance of StandardParagraph from the provided DocxPart and CTSdtContentRun objects. | |
| 66 | /// | |
| 67 | /// @param source the source DocxPart containing the paragraph content. | |
| 68 | /// @param paragraph the CTSdtContentRun object representing the content of the paragraph. | |
| 69 | /// | |
| 70 | /// @return a new instance of StandardParagraph constructed based on the provided DocxPart and paragraph. | |
| 71 | public static StandardParagraph from(DocxPart source, CTSdtContentRun paragraph) { | |
| 72 | var parent = (SdtRun) paragraph.getParent(); | |
| 73 | var parentParent = (P) parent.getParent(); | |
| 74 |
1
1. from : replaced return value with null for pro/verron/officestamper/core/StandardParagraph::from → TIMED_OUT |
return new StandardParagraph(source, paragraph, (ArrayListWml<Object>) parentParent.getContent()); |
| 75 | } | |
| 76 | ||
| 77 | /// Replaces a set of paragraph elements with new ones within the current paragraph's siblings. Ensures that the | |
| 78 | /// elements to be removed are replaced in the appropriate position. | |
| 79 | /// | |
| 80 | /// @param toRemove the list of paragraph elements to be removed. | |
| 81 | /// @param toAdd the list of paragraph elements to be added. | |
| 82 | /// | |
| 83 | /// @throws OfficeStamperException if the current paragraph object is not found in its siblings. | |
| 84 | @Override | |
| 85 | public void replace(List<P> toRemove, List<P> toAdd) { | |
| 86 | var siblings = siblings(); | |
| 87 | int index = siblings.indexOf(p.getParent()); | |
| 88 |
2
1. replace : negated conditional → NO_COVERAGE 2. replace : changed conditional boundary → NO_COVERAGE |
if (index < 0) throw new OfficeStamperException("Impossible"); |
| 89 | siblings.addAll(index, toAdd); | |
| 90 | siblings.removeAll(toRemove); | |
| 91 | } | |
| 92 | ||
| 93 | private List<Object> siblings() { | |
| 94 |
1
1. siblings : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::siblings → NO_COVERAGE |
return this.parent(ContentAccessor.class, 1) |
| 95 | .orElseThrow(throwing("This paragraph direct parent is not a classic parent object")) | |
| 96 | .getContent(); | |
| 97 | } | |
| 98 | ||
| 99 | private <T> Optional<T> parent(Class<T> aClass, int depth) { | |
| 100 |
1
1. parent : replaced return value with Optional.empty for pro/verron/officestamper/core/StandardParagraph::parent → KILLED |
return getFirstParentWithClass((Child) p.getParent(), aClass, depth); |
| 101 | } | |
| 102 | ||
| 103 | /// Removes the paragraph represented by the current instance. Delegates the removal process to a utility method | |
| 104 | /// that handles the underlying P object. | |
| 105 | @Override | |
| 106 | public void remove() { | |
| 107 |
1
1. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → KILLED |
WmlUtils.remove((Child) p.getParent()); |
| 108 | } | |
| 109 | ||
| 110 | @Override | |
| 111 | public void replace(String expression, Insert insert) { | |
| 112 |
1
1. lambda$replace$0 : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::lambda$replace$0 → NO_COVERAGE |
var newContents = WmlUtils.replaceExpressionWithRun(() -> p, expression, insert.elements(), insert::setRPr); |
| 113 | var content = contents.getContent(); | |
| 114 |
1
1. replace : removed call to java/util/List::clear → NO_COVERAGE |
content.clear(); |
| 115 | content.addAll(newContents); | |
| 116 | } | |
| 117 | ||
| 118 | @Override | |
| 119 | public void replace(Object start, Object end, Insert insert) { | |
| 120 | var content = contents.getContent(); | |
| 121 | var fromIndex = content.indexOf(start); | |
| 122 | var toIndex = content.indexOf(end); | |
| 123 |
2
1. replace : changed conditional boundary → TIMED_OUT 2. replace : negated conditional → KILLED |
if (fromIndex < 0) { |
| 124 | var msg = "The start element (%s) is not in the paragraph (%s)"; | |
| 125 | throw new OfficeStamperException(msg.formatted(start, this)); | |
| 126 | } | |
| 127 |
2
1. replace : changed conditional boundary → TIMED_OUT 2. replace : negated conditional → KILLED |
if (toIndex < 0) { |
| 128 | var msg = "The end element (%s) is not in the paragraph (%s)"; | |
| 129 | throw new OfficeStamperException(msg.formatted(end, this)); | |
| 130 | } | |
| 131 |
2
1. replace : changed conditional boundary → TIMED_OUT 2. replace : negated conditional → KILLED |
if (fromIndex > toIndex) { |
| 132 | var msg = "The start element (%s) is after the end element (%s)"; | |
| 133 | throw new OfficeStamperException(msg.formatted(end, this)); | |
| 134 | } | |
| 135 | var expression = extractExpression(start, end); | |
| 136 |
1
1. lambda$replace$1 : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::lambda$replace$1 → KILLED |
var newContents = WmlUtils.replaceExpressionWithRun(() -> p, expression, insert.elements(), insert::setRPr); |
| 137 |
1
1. replace : removed call to java/util/List::clear → KILLED |
content.clear(); |
| 138 | content.addAll(newContents); | |
| 139 | } | |
| 140 | ||
| 141 | private String extractExpression(Object from, Object to) { | |
| 142 | var content = contents.getContent(); | |
| 143 | var fromIndex = content.indexOf(from); | |
| 144 | var toIndex = content.indexOf(to); | |
| 145 |
1
1. extractExpression : Replaced integer addition with subtraction → KILLED |
var subContent = content.subList(fromIndex, toIndex + 1); |
| 146 |
1
1. lambda$extractExpression$0 : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::lambda$extractExpression$0 → KILLED |
ContentAccessor contentAccessor = () -> subContent; |
| 147 |
1
1. extractExpression : replaced return value with "" for pro/verron/officestamper/core/StandardParagraph::extractExpression → KILLED |
return new DocxIterator(contentAccessor).selectClass(R.class) |
| 148 | .map(WmlUtils::asString) | |
| 149 | .collect(joining()); | |
| 150 | } | |
| 151 | ||
| 152 | /// Returns the aggregated text over all runs. | |
| 153 | /// | |
| 154 | /// @return the text of all runs. | |
| 155 | @Override | |
| 156 | public String asString() { | |
| 157 |
1
1. asString : replaced return value with "" for pro/verron/officestamper/core/StandardParagraph::asString → KILLED |
return WmlUtils.asString(contents); |
| 158 | } | |
| 159 | ||
| 160 | /// Applies the given consumer to the paragraph represented by the current instance. This method facilitates custom | |
| 161 | /// processing by allowing the client to define specific operations to be performed on the paragraph's internal | |
| 162 | /// structure. | |
| 163 | /// | |
| 164 | /// @param pConsumer the consumer function to apply to the paragraph's structure. | |
| 165 | @Override | |
| 166 | public void apply(Consumer<ContentAccessor> pConsumer) { | |
| 167 |
2
1. apply : removed call to java/util/function/Consumer::accept → KILLED 2. lambda$apply$0 : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::lambda$apply$0 → KILLED |
pConsumer.accept(() -> p); |
| 168 | } | |
| 169 | ||
| 170 | /// Retrieves the nearest parent of the specified type for the current paragraph. The search is performed starting | |
| 171 | /// from the current paragraph and traversing up to the root, with a default maximum depth of Integer.MAX_VALUE. | |
| 172 | /// | |
| 173 | /// @param aClass the class type of the parent to search for | |
| 174 | /// @param <T> the generic type of the parent | |
| 175 | /// | |
| 176 | /// @return an Optional containing the parent of the specified type if found, or an empty Optional if no parent of | |
| 177 | /// the given type exists | |
| 178 | @Override | |
| 179 | public <T> Optional<T> parent(Class<T> aClass) { | |
| 180 |
1
1. parent : replaced return value with Optional.empty for pro/verron/officestamper/core/StandardParagraph::parent → KILLED |
return parent(aClass, Integer.MAX_VALUE); |
| 181 | } | |
| 182 | ||
| 183 | /// Retrieves the collection of comments associated with the current paragraph. | |
| 184 | /// | |
| 185 | /// @return a collection of [Comments.Comment] objects related to the paragraph. | |
| 186 | @Override | |
| 187 | public Collection<Comments.Comment> getComment() { | |
| 188 |
2
1. lambda$getComment$0 : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::lambda$getComment$0 → NO_COVERAGE 2. getComment : replaced return value with Collections.emptyList for pro/verron/officestamper/core/StandardParagraph::getComment → NO_COVERAGE |
return CommentUtil.getCommentFor(() -> p, part.document()); |
| 189 | } | |
| 190 | ||
| 191 | @Override | |
| 192 | public Optional<Table.Row> parentTableRow() { | |
| 193 |
2
1. lambda$parentTableRow$0 : replaced return value with null for pro/verron/officestamper/core/StandardParagraph::lambda$parentTableRow$0 → KILLED 2. parentTableRow : replaced return value with Optional.empty for pro/verron/officestamper/core/StandardParagraph::parentTableRow → KILLED |
return parent(Tr.class).map((Tr tr) -> new StandardRow(part, (Tbl) tr.getParent(), tr)); |
| 194 | } | |
| 195 | ||
| 196 | @Override | |
| 197 | public Optional<Table> parentTable() { | |
| 198 |
1
1. parentTable : replaced return value with Optional.empty for pro/verron/officestamper/core/StandardParagraph::parentTable → KILLED |
return parent(Tbl.class).map(StandardTable::new); |
| 199 | } | |
| 200 | ||
| 201 | /// Returns the string representation of the paragraph. This method delegates to the `asString` method to aggregate | |
| 202 | /// the text content of all runs. | |
| 203 | /// | |
| 204 | /// @return a string containing the combined text content of the paragraph's runs. | |
| 205 | @Override | |
| 206 | public String toString() { | |
| 207 |
1
1. toString : replaced return value with "" for pro/verron/officestamper/core/StandardParagraph::toString → NO_COVERAGE |
return asString(); |
| 208 | } | |
| 209 | } | |
Mutations | ||
| 46 |
1.1 |
|
| 49 |
1.1 |
|
| 62 |
1.1 |
|
| 74 |
1.1 |
|
| 88 |
1.1 2.2 |
|
| 94 |
1.1 |
|
| 100 |
1.1 |
|
| 107 |
1.1 |
|
| 112 |
1.1 |
|
| 114 |
1.1 |
|
| 123 |
1.1 2.2 |
|
| 127 |
1.1 2.2 |
|
| 131 |
1.1 2.2 |
|
| 136 |
1.1 |
|
| 137 |
1.1 |
|
| 145 |
1.1 |
|
| 146 |
1.1 |
|
| 147 |
1.1 |
|
| 157 |
1.1 |
|
| 167 |
1.1 2.2 |
|
| 180 |
1.1 |
|
| 188 |
1.1 2.2 |
|
| 193 |
1.1 2.2 |
|
| 198 |
1.1 |
|
| 207 |
1.1 |