1 | package pro.verron.officestamper.core; | |
2 | ||
3 | import jakarta.xml.bind.JAXBElement; | |
4 | import org.docx4j.TraversalUtil; | |
5 | import org.docx4j.XmlUtils; | |
6 | import org.docx4j.finders.ClassFinder; | |
7 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
8 | import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage; | |
9 | import org.docx4j.wml.*; | |
10 | import org.jvnet.jaxb2_commons.ppp.Child; | |
11 | import pro.verron.officestamper.api.DocxPart; | |
12 | import pro.verron.officestamper.api.OfficeStamperException; | |
13 | ||
14 | import java.util.*; | |
15 | import java.util.stream.Stream; | |
16 | ||
17 | /** | |
18 | * Utility class to retrieve elements from a document. | |
19 | * | |
20 | * @author Joseph Verron | |
21 | * @author DallanMC | |
22 | * @version ${version} | |
23 | * @since 1.4.7 | |
24 | */ | |
25 | public class DocumentUtil { | |
26 | ||
27 | private DocumentUtil() { | |
28 | throw new OfficeStamperException("Utility classes shouldn't be instantiated"); | |
29 | } | |
30 | ||
31 | ||
32 | public static <T> Stream<T> streamObjectElements( | |
33 | DocxPart source, | |
34 | Class<T> elementClass | |
35 | ) { | |
36 | ClassFinder finder = new ClassFinder(elementClass); | |
37 |
1
1. streamObjectElements : removed call to org/docx4j/TraversalUtil::visit → KILLED |
TraversalUtil.visit(source.part(), finder); |
38 |
1
1. streamObjectElements : replaced return value with Stream.empty for pro/verron/officestamper/core/DocumentUtil::streamObjectElements → KILLED |
return finder.results.stream() |
39 | .map(elementClass::cast); | |
40 | } | |
41 | ||
42 | /** | |
43 | * Retrieve the first element from an object. | |
44 | * | |
45 | * @param subDocument the object to get the first element from | |
46 | * | |
47 | * @return the first element | |
48 | */ | |
49 | public static Object lastElement(WordprocessingMLPackage subDocument) { | |
50 | var mainDocumentPart = subDocument.getMainDocumentPart(); | |
51 | var mainDocumentPartContent = mainDocumentPart.getContent(); | |
52 |
2
1. lastElement : Replaced integer subtraction with addition → KILLED 2. lastElement : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::lastElement → KILLED |
return mainDocumentPartContent.get(mainDocumentPartContent.size() - 1); |
53 | } | |
54 | ||
55 | /** | |
56 | * Retrieve the last element from an object. | |
57 | * | |
58 | * @param subDocument the object to get the last element from | |
59 | * | |
60 | * @return the last element | |
61 | */ | |
62 | public static List<Object> allElements(WordprocessingMLPackage subDocument) { | |
63 |
1
1. allElements : replaced return value with Collections.emptyList for pro/verron/officestamper/core/DocumentUtil::allElements → KILLED |
return subDocument.getMainDocumentPart() |
64 | .getContent(); | |
65 | } | |
66 | ||
67 | /** | |
68 | * Recursively walk through a source to find embedded images and import them in the target document. | |
69 | * | |
70 | * @param source source document containing image files. | |
71 | * @param target target document to add image files to. | |
72 | * | |
73 | * @return a {@link Map} object | |
74 | */ | |
75 | public static Map<R, R> walkObjectsAndImportImages( | |
76 | WordprocessingMLPackage source, | |
77 | WordprocessingMLPackage target | |
78 | ) { | |
79 |
1
1. walkObjectsAndImportImages : replaced return value with Collections.emptyMap for pro/verron/officestamper/core/DocumentUtil::walkObjectsAndImportImages → KILLED |
return walkObjectsAndImportImages(source.getMainDocumentPart(), |
80 | source, | |
81 | target); | |
82 | } | |
83 | ||
84 | /** | |
85 | * Recursively walk through source accessor to find embedded images and import the target document. | |
86 | * | |
87 | * @param container source container to walk. | |
88 | * @param source source document containing image files. | |
89 | * @param target target document to add image files to. | |
90 | * | |
91 | * @return a {@link Map} object | |
92 | */ | |
93 | public static Map<R, R> walkObjectsAndImportImages( | |
94 | ContentAccessor container, | |
95 | WordprocessingMLPackage source, | |
96 | WordprocessingMLPackage target | |
97 | ) { | |
98 | Map<R, R> replacements = new HashMap<>(); | |
99 | for (Object obj : container.getContent()) { | |
100 | Queue<Object> queue = new ArrayDeque<>(); | |
101 | queue.add(obj); | |
102 | ||
103 |
1
1. walkObjectsAndImportImages : negated conditional → KILLED |
while (!queue.isEmpty()) { |
104 | Object currentObj = queue.remove(); | |
105 | ||
106 |
2
1. walkObjectsAndImportImages : negated conditional → KILLED 2. walkObjectsAndImportImages : negated conditional → KILLED |
if (currentObj instanceof R currentR && isImageRun(currentR)) { |
107 | var docxImageExtractor = new DocxImageExtractor(source); | |
108 | var imageData = docxImageExtractor.getRunDrawingData(currentR); | |
109 | var maxWidth = docxImageExtractor.getRunDrawingMaxWidth(currentR); | |
110 | var imagePart = tryCreateImagePart(target, imageData); | |
111 | var runWithImage = RunUtil.createRunWithImage(maxWidth, imagePart); | |
112 | replacements.put(currentR, runWithImage); | |
113 | } | |
114 |
1
1. walkObjectsAndImportImages : negated conditional → KILLED |
else if (currentObj instanceof ContentAccessor contentAccessor) |
115 | queue.addAll(contentAccessor.getContent()); | |
116 | } | |
117 | } | |
118 |
1
1. walkObjectsAndImportImages : replaced return value with Collections.emptyMap for pro/verron/officestamper/core/DocumentUtil::walkObjectsAndImportImages → KILLED |
return replacements; |
119 | } | |
120 | ||
121 | /** | |
122 | * Check if a run contains an embedded image. | |
123 | * | |
124 | * @param run the run to analyze | |
125 | * | |
126 | * @return true if the run contains an image, false otherwise. | |
127 | */ | |
128 | private static boolean isImageRun(R run) { | |
129 |
2
1. isImageRun : replaced boolean return with false for pro/verron/officestamper/core/DocumentUtil::isImageRun → KILLED 2. isImageRun : replaced boolean return with true for pro/verron/officestamper/core/DocumentUtil::isImageRun → KILLED |
return run.getContent() |
130 | .stream() | |
131 | .filter(JAXBElement.class::isInstance) | |
132 | .map(JAXBElement.class::cast) | |
133 | .map(JAXBElement::getValue) | |
134 | .anyMatch(Drawing.class::isInstance); | |
135 | } | |
136 | ||
137 | private static BinaryPartAbstractImage tryCreateImagePart( | |
138 | WordprocessingMLPackage destDocument, | |
139 | byte[] imageData | |
140 | ) { | |
141 | try { | |
142 |
1
1. tryCreateImagePart : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::tryCreateImagePart → KILLED |
return BinaryPartAbstractImage.createImagePart(destDocument, imageData); |
143 | } catch (Exception e) { | |
144 | throw new OfficeStamperException(e); | |
145 | } | |
146 | } | |
147 | ||
148 | /** | |
149 | * Finds the smallest common parent between two objects. | |
150 | * | |
151 | * @param o1 the first object | |
152 | * @param o2 the second object | |
153 | * | |
154 | * @return the smallest common parent of the two objects | |
155 | * | |
156 | * @throws OfficeStamperException if there is an error finding the common parent | |
157 | */ | |
158 | public static ContentAccessor findSmallestCommonParent(Object o1, Object o2) { | |
159 |
2
1. findSmallestCommonParent : negated conditional → KILLED 2. findSmallestCommonParent : negated conditional → KILLED |
if (depthElementSearch(o1, o2) && o2 instanceof ContentAccessor contentAccessor) |
160 |
1
1. findSmallestCommonParent : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::findSmallestCommonParent → KILLED |
return findInsertableParent(contentAccessor); |
161 |
1
1. findSmallestCommonParent : negated conditional → KILLED |
else if (o2 instanceof Child child) |
162 |
1
1. findSmallestCommonParent : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::findSmallestCommonParent → KILLED |
return findSmallestCommonParent(o1, child.getParent()); |
163 | else | |
164 | throw new OfficeStamperException(); | |
165 | } | |
166 | ||
167 | /** | |
168 | * Recursively searches for an element in a content tree. | |
169 | * | |
170 | * @param searchTarget the element to search for | |
171 | * @param content the content tree to search in | |
172 | * | |
173 | * @return true if the element is found, false otherwise | |
174 | */ | |
175 | public static boolean depthElementSearch(Object searchTarget, Object content) { | |
176 | content = XmlUtils.unwrap(content); | |
177 |
1
1. depthElementSearch : negated conditional → KILLED |
if (searchTarget.equals(content)) { |
178 |
1
1. depthElementSearch : replaced boolean return with false for pro/verron/officestamper/core/DocumentUtil::depthElementSearch → KILLED |
return true; |
179 | } | |
180 |
1
1. depthElementSearch : negated conditional → KILLED |
else if (content instanceof ContentAccessor contentAccessor) { |
181 | for (Object object : contentAccessor.getContent()) { | |
182 | Object unwrappedObject = XmlUtils.unwrap(object); | |
183 |
1
1. depthElementSearch : negated conditional → KILLED |
if (searchTarget.equals(unwrappedObject) |
184 |
1
1. depthElementSearch : negated conditional → KILLED |
|| depthElementSearch(searchTarget, unwrappedObject)) { |
185 |
1
1. depthElementSearch : replaced boolean return with false for pro/verron/officestamper/core/DocumentUtil::depthElementSearch → KILLED |
return true; |
186 | } | |
187 | } | |
188 | } | |
189 |
1
1. depthElementSearch : replaced boolean return with true for pro/verron/officestamper/core/DocumentUtil::depthElementSearch → KILLED |
return false; |
190 | } | |
191 | ||
192 | private static ContentAccessor findInsertableParent(Object searchFrom) { | |
193 |
1
1. findInsertableParent : negated conditional → KILLED |
if (searchFrom instanceof Tc tc) |
194 |
1
1. findInsertableParent : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::findInsertableParent → NO_COVERAGE |
return tc; |
195 |
1
1. findInsertableParent : negated conditional → KILLED |
else if (searchFrom instanceof Body body) |
196 |
1
1. findInsertableParent : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::findInsertableParent → KILLED |
return body; |
197 |
1
1. findInsertableParent : negated conditional → KILLED |
else if (searchFrom instanceof Child child) |
198 |
1
1. findInsertableParent : replaced return value with null for pro/verron/officestamper/core/DocumentUtil::findInsertableParent → KILLED |
return findInsertableParent(child.getParent()); |
199 | else | |
200 | throw new OfficeStamperException(); | |
201 | } | |
202 | } | |
Mutations | ||
37 |
1.1 |
|
38 |
1.1 |
|
52 |
1.1 2.2 |
|
63 |
1.1 |
|
79 |
1.1 |
|
103 |
1.1 |
|
106 |
1.1 2.2 |
|
114 |
1.1 |
|
118 |
1.1 |
|
129 |
1.1 2.2 |
|
142 |
1.1 |
|
159 |
1.1 2.2 |
|
160 |
1.1 |
|
161 |
1.1 |
|
162 |
1.1 |
|
177 |
1.1 |
|
178 |
1.1 |
|
180 |
1.1 |
|
183 |
1.1 |
|
184 |
1.1 |
|
185 |
1.1 |
|
189 |
1.1 |
|
193 |
1.1 |
|
194 |
1.1 |
|
195 |
1.1 |
|
196 |
1.1 |
|
197 |
1.1 |
|
198 |
1.1 |