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