OpenpackagingUtils.java
package pro.verron.officestamper.utils.openpackaging;
import org.docx4j.openpackaging.contenttype.ContentType;
import org.docx4j.openpackaging.contenttype.ContentTypeManager;
import org.docx4j.openpackaging.contenttype.ContentTypes;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.exceptions.PartUnrecognisedException;
import org.docx4j.openpackaging.packages.OpcPackage;
import org.docx4j.openpackaging.packages.PresentationMLPackage;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.DefaultXmlPart;
import org.docx4j.openpackaging.parts.Part;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.XmlPart;
import org.docx4j.openpackaging.parts.relationships.Namespaces;
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
import org.w3c.dom.Document;
import pro.verron.officestamper.utils.UtilsException;
import pro.verron.officestamper.utils.image.ImgPart;
import pro.verron.officestamper.utils.image.ImgUtils;
import pro.verron.officestamper.utils.svg.SvgUtils;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import static pro.verron.officestamper.utils.openpackaging.OpenpackagingFactory.setupRelationship;
/// Utility class for working with Open Packaging documents. This class provides methods to load and export Word
/// documents using DOCX4J
public class OpenpackagingUtils {
private OpenpackagingUtils() {
throw new UtilsException("Utility class shouldn't be instantiated");
}
/// Loads a Word document from the provided input stream.
///
/// @param is the input stream containing the Word document data
///
/// @return a WordprocessingMLPackage representing the loaded document
///
/// @throws UtilsException if there is an error loading the document
public static WordprocessingMLPackage loadWord(InputStream is) {
try {
return WordprocessingMLPackage.load(is);
} catch (Docx4JException e) {
throw new UtilsException(e);
}
}
/// Exports a Word document to the provided output stream.
///
/// @param wordprocessingMLPackage the Word document to export
/// @param os the output stream to write the document to
///
/// @throws UtilsException if there is an error exporting the document
public static void exportWord(WordprocessingMLPackage wordprocessingMLPackage, OutputStream os) {
try {
wordprocessingMLPackage.save(os);
} catch (Docx4JException e) {
throw new UtilsException(e);
}
}
/// Loads a PowerPoint document from the provided input stream.
///
/// @param is the input stream containing the PowerPoint document data
///
/// @return a PresentationMLPackage representing the loaded document
///
/// @throws UtilsException if there is an error loading the document
public static PresentationMLPackage loadPowerPoint(InputStream is) {
try {
return PresentationMLPackage.load(is);
} catch (Docx4JException e) {
throw new UtilsException(e);
}
}
/// Exports a PowerPoint document to the provided output stream.
///
/// @param presentationMLPackage the PowerPoint document to export
/// @param os the output stream to write the document to
///
/// @throws UtilsException if there is an error exporting the document
public static void exportPowerPoint(PresentationMLPackage presentationMLPackage, OutputStream os) {
try {
presentationMLPackage.save(os);
} catch (Docx4JException e) {
throw new UtilsException(e);
}
}
public static Optional<ImgPart> findImgPart(OpcPackage opcPackage, Part sourcePart, byte[] bytes) {
if (bytes.length == 0) throw new UtilsException("Can't create image from empty byte array");
var format = ImgUtils.detectFormat(bytes)
.orElseThrow(() -> new UtilsException("Could not detect a supported image type."));
var mimeType = ImgUtils.supportedContentType(format.name())
.orElseThrow(() -> new UtilsException("Unsupported image " + "type"));
ensureHasRelationshipPart(sourcePart);
var relationshipId = createRelationshipId(sourcePart);
var ctm = opcPackage.getContentTypeManager();
var isSvg = mimeType.equals(ContentTypes.IMAGE_SVG);
var parts = sourcePart.getPackage()
.getParts()
.getParts();
for (var part : parts.values()) {
switch (part) {
case DefaultXmlPart xmlPart when isSvg -> {
var existingXml = extractXml(xmlPart);
var newXml = extractXml(bytes, ctm);
if (Objects.equals(newXml, existingXml)) {
var relationship = setupRelationship(sourcePart, xmlPart, relationshipId);
return Optional.of(new ImgPart(format, relationship));
}
}
case BinaryPartAbstractImage imagePart -> {
if (Arrays.equals(imagePart.getBytes(), bytes)) {
var relationship = setupRelationship(sourcePart, imagePart, relationshipId);
return Optional.of(new ImgPart(format, relationship));
}
}
case null, default -> { /* DO NOTHING */ }
}
}
return Optional.empty();
}
static void ensureHasRelationshipPart(Part sourcePart) {
if (sourcePart.getRelationshipsPart() == null) RelationshipsPart.createRelationshipsPartForPart(sourcePart);
}
static String createRelationshipId(Part sourcePart) {
var relationshipsPart = sourcePart.getRelationshipsPart();
return relationshipsPart.getNextId();
}
private static String extractXml(XmlPart xmlPart) {
String existingXml;
try {
existingXml = xmlPart.getXML();
} catch (Docx4JException e) {
throw new RuntimeException(e);
}
return existingXml;
}
private static String extractXml(byte[] bytes, ContentTypeManager ctm) {
var newDocument = SvgUtils.parseDocument(bytes);
var svgPart = createSvgPart(newDocument, ctm, "/temporary");
return extractXml(svgPart);
}
static XmlPart createSvgPart(
Document document,
ContentTypeManager contentTypeManager,
String partName
) {
XmlPart part;
try {
part = (XmlPart) contentTypeManager.newPartForContentType(ContentTypes.IMAGE_SVG, partName, null);
} catch (InvalidFormatException | PartUnrecognisedException e) {
throw new UtilsException(e);
}
part.setRelationshipType(Namespaces.IMAGE);
part.setContentType(new ContentType(ContentTypes.IMAGE_SVG));
part.setDocument(document);
return part;
}
}