mirror of https://github.com/apache/poi.git
#64773 - Visual signatures for .xlsx/.docx
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1882394 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5d2d6fee1a
commit
2a292cb42d
|
@ -792,7 +792,7 @@ under the License.
|
||||||
<copy todir="${xmlbean.xsds.dir}">
|
<copy todir="${xmlbean.xsds.dir}">
|
||||||
<zipfileset src="${ooxml.xsds.izip.1}"/>
|
<zipfileset src="${ooxml.xsds.izip.1}"/>
|
||||||
<fileset dir="${ooxml.visio.xsd.dir}"/>
|
<fileset dir="${ooxml.visio.xsd.dir}"/>
|
||||||
<fileset dir="${ooxml.schema.xsdconfig.dir}" includes="ooxmlSchemas.xsdconfig,markup-compatibility.xsd"/>
|
<fileset dir="${ooxml.schema.xsdconfig.dir}" includes="ooxmlSchemas.xsdconfig,markup-compatibility.xsd,vmlDrawing.xsd"/>
|
||||||
<zipfileset src="${ooxml.xsds.izip.2}" includes="opc-digSig.xsd,opc-relationships.xsd"/>
|
<zipfileset src="${ooxml.xsds.izip.2}" includes="opc-digSig.xsd,opc-relationships.xsd"/>
|
||||||
<fileset dir="${ooxml.security.xsd.dir}" includes="signatureInfo.xsd"/>
|
<fileset dir="${ooxml.security.xsd.dir}" includes="signatureInfo.xsd"/>
|
||||||
<fileset dir="${ooxml.schema.xsdconfig.dir}" includes="XAdES*.xsd,*.xsdconfig,xmldsig*.xsd"/>
|
<fileset dir="${ooxml.schema.xsdconfig.dir}" includes="XAdES*.xsd,*.xsdconfig,xmldsig*.xsd"/>
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.apache.poi.common.usermodel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General enum class to define a picture format/type
|
||||||
|
*
|
||||||
|
* @since POI 5.0
|
||||||
|
*/
|
||||||
|
public enum PictureType {
|
||||||
|
/** Extended windows meta file */
|
||||||
|
EMF("image/x-emf",".emf"),
|
||||||
|
/** Windows Meta File */
|
||||||
|
WMF("image/x-wmf",".wmf"),
|
||||||
|
/** Mac PICT format */
|
||||||
|
PICT("image/pict",".pict"), // or image/x-pict (for HSLF) ???
|
||||||
|
/** JPEG format */
|
||||||
|
JPEG("image/jpeg",".jpg"),
|
||||||
|
/** PNG format */
|
||||||
|
PNG("image/png",".png"),
|
||||||
|
/** Device independent bitmap */
|
||||||
|
DIB("image/dib",".dib"),
|
||||||
|
/** GIF image format */
|
||||||
|
GIF("image/gif",".gif"),
|
||||||
|
/** Tag Image File (.tiff) */
|
||||||
|
TIFF("image/tiff",".tif"),
|
||||||
|
/** Encapsulated Postscript (.eps) */
|
||||||
|
EPS("image/x-eps",".eps"),
|
||||||
|
/** Windows Bitmap (.bmp) */
|
||||||
|
BMP("image/x-ms-bmp",".bmp"),
|
||||||
|
/** WordPerfect graphics (.wpg) */
|
||||||
|
WPG("image/x-wpg",".wpg"),
|
||||||
|
/** Microsoft Windows Media Photo image (.wdp) */
|
||||||
|
WDP("image/vnd.ms-photo",".wdp"),
|
||||||
|
/** Scalable vector graphics (.svg) - supported by Office 2016 and higher */
|
||||||
|
SVG("image/svg+xml", ".svg"),
|
||||||
|
/** Unknown picture type - specific to escher bse record */
|
||||||
|
UNKNOWN("", ".dat"),
|
||||||
|
/** Picture type error - specific to escher bse record */
|
||||||
|
ERROR("", ".dat"),
|
||||||
|
/** JPEG in the YCCK or CMYK color space. */
|
||||||
|
CMYKJPEG("image/jpeg", ".jpg"),
|
||||||
|
/** client defined blip type - native-id 32 to 255 */
|
||||||
|
CLIENT("", ".dat")
|
||||||
|
;
|
||||||
|
|
||||||
|
public final String contentType,extension;
|
||||||
|
|
||||||
|
PictureType(String contentType,String extension) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.extension = extension;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -22,6 +22,7 @@ open module org.apache.poi.ooxml.schemas {
|
||||||
requires transitive org.apache.xmlbeans;
|
requires transitive org.apache.xmlbeans;
|
||||||
requires java.xml;
|
requires java.xml;
|
||||||
|
|
||||||
|
|
||||||
exports com.microsoft.schemas.compatibility;
|
exports com.microsoft.schemas.compatibility;
|
||||||
exports com.microsoft.schemas.office.excel;
|
exports com.microsoft.schemas.office.excel;
|
||||||
exports com.microsoft.schemas.office.office;
|
exports com.microsoft.schemas.office.office;
|
||||||
|
@ -29,6 +30,7 @@ open module org.apache.poi.ooxml.schemas {
|
||||||
exports com.microsoft.schemas.office.x2006.digsig;
|
exports com.microsoft.schemas.office.x2006.digsig;
|
||||||
exports com.microsoft.schemas.vml;
|
exports com.microsoft.schemas.vml;
|
||||||
exports org.apache.poi.schemas.ooxml.system.ooxml;
|
exports org.apache.poi.schemas.ooxml.system.ooxml;
|
||||||
|
exports org.apache.poi.schemas.vmldrawing;
|
||||||
exports org.etsi.uri.x01903.v13;
|
exports org.etsi.uri.x01903.v13;
|
||||||
exports org.openxmlformats.schemas.drawingml.x2006.chart;
|
exports org.openxmlformats.schemas.drawingml.x2006.chart;
|
||||||
exports org.openxmlformats.schemas.drawingml.x2006.main;
|
exports org.openxmlformats.schemas.drawingml.x2006.main;
|
||||||
|
|
Binary file not shown.
|
@ -56,4 +56,6 @@ open module org.apache.poi.ooxml.schemas {
|
||||||
exports org.openxmlformats.schemas.xpackage.x2006.digitalSignature;
|
exports org.openxmlformats.schemas.xpackage.x2006.digitalSignature;
|
||||||
exports org.openxmlformats.schemas.xpackage.x2006.relationships;
|
exports org.openxmlformats.schemas.xpackage.x2006.relationships;
|
||||||
exports org.w3.x2000.x09.xmldsig;
|
exports org.w3.x2000.x09.xmldsig;
|
||||||
|
|
||||||
|
exports org.apache.poi.schemas.vmldrawing;
|
||||||
}
|
}
|
|
@ -531,7 +531,8 @@ public class POIXMLDocumentPart {
|
||||||
* @param minIdx The minimum free index to assign, use -1 for any
|
* @param minIdx The minimum free index to assign, use -1 for any
|
||||||
* @return The next free part number, or -1 if none available
|
* @return The next free part number, or -1 if none available
|
||||||
*/
|
*/
|
||||||
protected final int getNextPartNumber(POIXMLRelation descriptor, int minIdx) {
|
@Internal
|
||||||
|
public final int getNextPartNumber(POIXMLRelation descriptor, int minIdx) {
|
||||||
OPCPackage pkg = packagePart.getPackage();
|
OPCPackage pkg = packagePart.getPackage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -17,14 +17,35 @@
|
||||||
|
|
||||||
package org.apache.poi.ooxml.util;
|
package org.apache.poi.ooxml.util;
|
||||||
|
|
||||||
import org.apache.poi.util.POILogFactory;
|
import java.util.Locale;
|
||||||
import org.apache.poi.util.POILogger;
|
|
||||||
|
|
||||||
import javax.xml.XMLConstants;
|
import javax.xml.XMLConstants;
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
|
|
||||||
|
import com.microsoft.schemas.compatibility.AlternateContentDocument;
|
||||||
|
import org.apache.poi.util.Internal;
|
||||||
|
import org.apache.poi.util.POILogFactory;
|
||||||
|
import org.apache.poi.util.POILogger;
|
||||||
|
import org.apache.poi.xslf.usermodel.XSLFShape;
|
||||||
|
import org.apache.xmlbeans.XmlCursor;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
import org.apache.xmlbeans.XmlObject;
|
||||||
|
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
|
||||||
|
|
||||||
public final class XPathHelper {
|
public final class XPathHelper {
|
||||||
private static POILogger logger = POILogFactory.getLogger(XPathHelper.class);
|
private static final POILogger LOG = POILogFactory.getLogger(XPathHelper.class);
|
||||||
|
|
||||||
|
private static final String OSGI_ERROR =
|
||||||
|
"Schemas (*.xsb) for <CLASS> can't be loaded - usually this happens when OSGI " +
|
||||||
|
"loading is used and the thread context classloader has no reference to " +
|
||||||
|
"the xmlbeans classes - please either verify if the <XSB>.xsb is on the " +
|
||||||
|
"classpath or alternatively try to use the full ooxml-schemas-x.x.jar";
|
||||||
|
|
||||||
|
private static final String MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
||||||
|
private static final String MAC_DML_NS = "http://schemas.microsoft.com/office/mac/drawingml/2008/main";
|
||||||
|
private static final QName ALTERNATE_CONTENT_TAG = new QName(MC_NS, "AlternateContent");
|
||||||
|
// AlternateContentDocument.AlternateContent.type.getName();
|
||||||
|
|
||||||
private XPathHelper() {}
|
private XPathHelper() {}
|
||||||
|
|
||||||
|
@ -41,9 +62,165 @@ public final class XPathHelper {
|
||||||
try {
|
try {
|
||||||
xpf.setFeature(feature, enabled);
|
xpf.setFeature(feature, enabled);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
|
LOG.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
|
||||||
} catch (AbstractMethodError ame) {
|
} catch (AbstractMethodError ame) {
|
||||||
logger.log(POILogger.WARN, "Cannot set XPathFactory feature because outdated XML parser in classpath", feature, ame);
|
LOG.log(POILogger.WARN, "Cannot set XPathFactory feature because outdated XML parser in classpath", feature, ame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal code - API may change any time!
|
||||||
|
* <p>
|
||||||
|
* The {@link #selectProperty(Class, String)} xquery method has some performance penalties,
|
||||||
|
* which can be workaround by using {@link XmlCursor}. This method also takes into account
|
||||||
|
* that {@code AlternateContent} tags can occur anywhere on the given path.
|
||||||
|
* <p>
|
||||||
|
* It returns the first element found - the search order is:
|
||||||
|
* <ul>
|
||||||
|
* <li>searching for a direct child</li>
|
||||||
|
* <li>searching for a AlternateContent.Choice child</li>
|
||||||
|
* <li>searching for a AlternateContent.Fallback child</li>
|
||||||
|
* </ul>
|
||||||
|
* Currently POI OOXML is based on the first edition of the ECMA 376 schema, which doesn't
|
||||||
|
* allow AlternateContent tags to show up everywhere. The factory flag is
|
||||||
|
* a workaround to process files based on a later edition. But it comes with the drawback:
|
||||||
|
* any change on the returned XmlObject aren't saved back to the underlying document -
|
||||||
|
* so it's a non updatable clone. If factory is null, a XmlException is
|
||||||
|
* thrown if the AlternateContent is not allowed by the surrounding element or if the
|
||||||
|
* extracted object is of the generic type XmlAnyTypeImpl.
|
||||||
|
*
|
||||||
|
* @param resultClass the requested result class
|
||||||
|
* @param factory a factory parse method reference to allow reparsing of elements
|
||||||
|
* extracted from AlternateContent elements. Usually the enclosing XmlBeans type needs to be used
|
||||||
|
* to parse the stream
|
||||||
|
* @param path the elements path, each array must contain at least 1 QName,
|
||||||
|
* but can contain additional alternative tags
|
||||||
|
* @return the xml object at the path location, or null if not found
|
||||||
|
*
|
||||||
|
* @throws XmlException If factory is null, a XmlException is
|
||||||
|
* thrown if the AlternateContent is not allowed by the surrounding element or if the
|
||||||
|
* extracted object is of the generic type XmlAnyTypeImpl.
|
||||||
|
*
|
||||||
|
* @since POI 4.1.2
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Internal
|
||||||
|
public static <T extends XmlObject> T selectProperty(XmlObject startObject, Class<T> resultClass, XSLFShape.ReparseFactory<T> factory, QName[]... path)
|
||||||
|
throws XmlException {
|
||||||
|
XmlObject xo = startObject;
|
||||||
|
XmlCursor cur = xo.newCursor();
|
||||||
|
XmlCursor innerCur = null;
|
||||||
|
try {
|
||||||
|
innerCur = selectProperty(cur, path, 0, factory != null, false);
|
||||||
|
if (innerCur == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pesky XmlBeans bug - see Bugzilla #49934
|
||||||
|
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
|
||||||
|
xo = innerCur.getObject();
|
||||||
|
if (xo instanceof XmlAnyTypeImpl) {
|
||||||
|
String errorTxt = OSGI_ERROR
|
||||||
|
.replace("<CLASS>", resultClass.getSimpleName())
|
||||||
|
.replace("<XSB>", resultClass.getSimpleName().toLowerCase(Locale.ROOT)+"*");
|
||||||
|
if (factory == null) {
|
||||||
|
throw new XmlException(errorTxt);
|
||||||
|
} else {
|
||||||
|
xo = factory.parse(innerCur.newXMLStreamReader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)xo;
|
||||||
|
} finally {
|
||||||
|
cur.dispose();
|
||||||
|
if (innerCur != null) {
|
||||||
|
innerCur.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlCursor selectProperty(final XmlCursor cur, final QName[][] path, final int offset, final boolean reparseAlternate, final boolean isAlternate)
|
||||||
|
throws XmlException {
|
||||||
|
// first try the direct children
|
||||||
|
for (QName qn : path[offset]) {
|
||||||
|
for (boolean found = cur.toChild(qn); found; found = cur.toNextSibling(qn)) {
|
||||||
|
if (offset == path.length-1) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
cur.push();
|
||||||
|
XmlCursor innerCur = selectProperty(cur, path, offset+1, reparseAlternate, false);
|
||||||
|
if (innerCur != null) {
|
||||||
|
return innerCur;
|
||||||
|
}
|
||||||
|
cur.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we were called inside an alternate content handling don't look for alternates again
|
||||||
|
if (isAlternate || !cur.toChild(ALTERNATE_CONTENT_TAG)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise check first the choice then the fallback content
|
||||||
|
XmlObject xo = cur.getObject();
|
||||||
|
AlternateContentDocument.AlternateContent alterCont;
|
||||||
|
if (xo instanceof AlternateContentDocument.AlternateContent) {
|
||||||
|
alterCont = (AlternateContentDocument.AlternateContent)xo;
|
||||||
|
} else {
|
||||||
|
// Pesky XmlBeans bug - see Bugzilla #49934
|
||||||
|
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
|
||||||
|
if (!reparseAlternate) {
|
||||||
|
throw new XmlException(OSGI_ERROR
|
||||||
|
.replace("<CLASS>", "AlternateContent")
|
||||||
|
.replace("<XSB>", "alternatecontentelement")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AlternateContentDocument acd = AlternateContentDocument.Factory.parse(cur.newXMLStreamReader());
|
||||||
|
alterCont = acd.getAlternateContent();
|
||||||
|
} catch (XmlException e) {
|
||||||
|
throw new XmlException("unable to parse AlternateContent element", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int choices = alterCont.sizeOfChoiceArray();
|
||||||
|
for (int i=0; i<choices; i++) {
|
||||||
|
// TODO: check [Requires] attribute of [Choice] element, if we can handle the content
|
||||||
|
AlternateContentDocument.AlternateContent.Choice choice = alterCont.getChoiceArray(i);
|
||||||
|
XmlCursor cCur = choice.newCursor();
|
||||||
|
XmlCursor innerCur = null;
|
||||||
|
try {
|
||||||
|
String requiresNS = cCur.namespaceForPrefix(choice.getRequires());
|
||||||
|
if (MAC_DML_NS.equalsIgnoreCase(requiresNS)) {
|
||||||
|
// Mac DML usually contains PDFs ...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
innerCur = selectProperty(cCur, path, offset, reparseAlternate, true);
|
||||||
|
if (innerCur != null) {
|
||||||
|
return innerCur;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (innerCur != cCur) {
|
||||||
|
cCur.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alterCont.isSetFallback()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlCursor fCur = alterCont.getFallback().newCursor();
|
||||||
|
XmlCursor innerCur = null;
|
||||||
|
try {
|
||||||
|
innerCur = selectProperty(fCur, path, offset, reparseAlternate, true);
|
||||||
|
return innerCur;
|
||||||
|
} finally {
|
||||||
|
if (innerCur != fCur) {
|
||||||
|
fCur.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||||
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
|
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
|
||||||
|
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
|
import org.apache.poi.hpsf.ClassID;
|
||||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
|
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
|
||||||
|
@ -89,10 +90,10 @@ public class SignatureConfig {
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
private ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
|
private final ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
|
||||||
private ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
|
private final ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
|
||||||
private ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
|
private final ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
|
||||||
private ThreadLocal<Provider> provider = new ThreadLocal<>();
|
private final ThreadLocal<Provider> provider = new ThreadLocal<>();
|
||||||
|
|
||||||
private List<SignatureFacet> signatureFacets = new ArrayList<>();
|
private List<SignatureFacet> signatureFacets = new ArrayList<>();
|
||||||
private HashAlgorithm digestAlgo = HashAlgorithm.sha256;
|
private HashAlgorithm digestAlgo = HashAlgorithm.sha256;
|
||||||
|
@ -165,6 +166,26 @@ public class SignatureConfig {
|
||||||
*/
|
*/
|
||||||
private String signatureDescription = "Office OpenXML Document";
|
private String signatureDescription = "Office OpenXML Document";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only applies when working with visual signatures:
|
||||||
|
* Specifies a GUID which can be cross-referenced with the GUID of the signature line stored in the document content.
|
||||||
|
* I.e. the signatureline element id attribute in the document/sheet has to be references in the SetupId element.
|
||||||
|
*/
|
||||||
|
private ClassID signatureImageSetupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a signature image for visual signature lines
|
||||||
|
*/
|
||||||
|
private byte[] signatureImage;
|
||||||
|
/**
|
||||||
|
* The image shown, when the signature is valid
|
||||||
|
*/
|
||||||
|
private byte[] signatureImageValid;
|
||||||
|
/**
|
||||||
|
* The image shown, when the signature is invalid
|
||||||
|
*/
|
||||||
|
private byte[] signatureImageInvalid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The process of signing includes the marshalling of xml structures.
|
* The process of signing includes the marshalling of xml structures.
|
||||||
* This also includes the canonicalization. Currently this leads to problems
|
* This also includes the canonicalization. Currently this leads to problems
|
||||||
|
@ -386,6 +407,38 @@ public class SignatureConfig {
|
||||||
this.signatureDescription = signatureDescription;
|
this.signatureDescription = signatureDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getSignatureImage() {
|
||||||
|
return signatureImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignatureImageValid() {
|
||||||
|
return signatureImageValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSignatureImageInvalid() {
|
||||||
|
return signatureImageInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassID getSignatureImageSetupId() {
|
||||||
|
return signatureImageSetupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureImageSetupId(ClassID signatureImageSetupId) {
|
||||||
|
this.signatureImageSetupId = signatureImageSetupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureImage(byte[] signatureImage) {
|
||||||
|
this.signatureImage = (signatureImage == null) ? null : signatureImage.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureImageValid(byte[] signatureImageValid) {
|
||||||
|
this.signatureImageValid = (signatureImageValid == null) ? null : signatureImageValid.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureImageInvalid(byte[] signatureImageInvalid) {
|
||||||
|
this.signatureImageInvalid = (signatureImageInvalid == null) ? null : signatureImageInvalid.clone();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the default canonicalization method, defaults to INCLUSIVE
|
* @return the default canonicalization method, defaults to INCLUSIVE
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,489 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.poifs.crypt.dsig;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.GradientPaint;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.LineBreakMeasurer;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.font.TextLayout;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Dimension2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.AttributedCharacterIterator;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import com.microsoft.schemas.office.office.CTSignatureLine;
|
||||||
|
import com.microsoft.schemas.office.office.STTrueFalse;
|
||||||
|
import com.microsoft.schemas.vml.CTGroup;
|
||||||
|
import com.microsoft.schemas.vml.CTImageData;
|
||||||
|
import com.microsoft.schemas.vml.CTShape;
|
||||||
|
import com.microsoft.schemas.vml.STExt;
|
||||||
|
import org.apache.poi.common.usermodel.PictureType;
|
||||||
|
import org.apache.poi.hpsf.ClassID;
|
||||||
|
import org.apache.poi.ooxml.POIXMLException;
|
||||||
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
import org.apache.poi.poifs.filesystem.FileMagic;
|
||||||
|
import org.apache.poi.sl.draw.DrawPictureShape;
|
||||||
|
import org.apache.poi.sl.draw.ImageRenderer;
|
||||||
|
import org.apache.xmlbeans.XmlCursor;
|
||||||
|
import org.apache.xmlbeans.XmlObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for SignatureLines (XSSF,XWPF only)
|
||||||
|
*/
|
||||||
|
public abstract class SignatureLine {
|
||||||
|
|
||||||
|
private static final String MS_OFFICE_URN = "urn:schemas-microsoft-com:office:office";
|
||||||
|
protected static final QName QNAME_SIGNATURE_LINE = new QName(MS_OFFICE_URN, "signatureline");
|
||||||
|
|
||||||
|
|
||||||
|
private ClassID setupId;
|
||||||
|
private Boolean allowComments;
|
||||||
|
private String signingInstructions = "Before signing the document, verify that the content you are signing is correct.";
|
||||||
|
private String suggestedSigner;
|
||||||
|
private String suggestedSigner2;
|
||||||
|
private String suggestedSignerEmail;
|
||||||
|
private String caption;
|
||||||
|
private String invalidStamp = "invalid";
|
||||||
|
private byte[] plainSignature;
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
private CTShape signatureShape;
|
||||||
|
|
||||||
|
public ClassID getSetupId() {
|
||||||
|
return setupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSetupId(ClassID setupId) {
|
||||||
|
this.setupId = setupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getAllowComments() {
|
||||||
|
return allowComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowComments(Boolean allowComments) {
|
||||||
|
this.allowComments = allowComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSigningInstructions() {
|
||||||
|
return signingInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSigningInstructions(String signingInstructions) {
|
||||||
|
this.signingInstructions = signingInstructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSuggestedSigner() {
|
||||||
|
return suggestedSigner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuggestedSigner(String suggestedSigner) {
|
||||||
|
this.suggestedSigner = suggestedSigner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSuggestedSigner2() {
|
||||||
|
return suggestedSigner2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuggestedSigner2(String suggestedSigner2) {
|
||||||
|
this.suggestedSigner2 = suggestedSigner2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSuggestedSignerEmail() {
|
||||||
|
return suggestedSignerEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuggestedSignerEmail(String suggestedSignerEmail) {
|
||||||
|
this.suggestedSignerEmail = suggestedSignerEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default caption
|
||||||
|
* @return "[suggestedSigner] \n [suggestedSigner2] \n [suggestedSignerEmail]"
|
||||||
|
*/
|
||||||
|
public String getDefaultCaption() {
|
||||||
|
return suggestedSigner+"\n"+suggestedSigner2+"\n"+suggestedSignerEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caption - use maximum of three lines separated by "\n".
|
||||||
|
* Defaults to {@link #getDefaultCaption()}
|
||||||
|
* @param caption the signature caption
|
||||||
|
*/
|
||||||
|
public void setCaption(String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInvalidStamp() {
|
||||||
|
return invalidStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text stamped over the signature image when the document got tampered with
|
||||||
|
* @param invalidStamp the invalid stamp text
|
||||||
|
*/
|
||||||
|
public void setInvalidStamp(String invalidStamp) {
|
||||||
|
this.invalidStamp = invalidStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the plain signature without caption */
|
||||||
|
public byte[] getPlainSignature() {
|
||||||
|
return plainSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the plain signature
|
||||||
|
* supported formats are PNG,GIF,JPEG,(SVG),EMF,WMF.
|
||||||
|
* for SVG,EMF,WMF poi-scratchpad needs to be in the class-/modulepath
|
||||||
|
*
|
||||||
|
* @param plainSignature the plain signature - if {@code null}, the signature is not rendered
|
||||||
|
* and only the caption is visible
|
||||||
|
*/
|
||||||
|
public void setPlainSignature(byte[] plainSignature) {
|
||||||
|
this.plainSignature = plainSignature;
|
||||||
|
this.contentType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentType(String contentType) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CTShape getSignatureShape() {
|
||||||
|
return signatureShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureShape(CTShape signatureShape) {
|
||||||
|
this.signatureShape = signatureShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureShape(CTSignatureLine signatureLine) {
|
||||||
|
XmlCursor cur = signatureLine.newCursor();
|
||||||
|
cur.toParent();
|
||||||
|
this.signatureShape = (CTShape)cur.getObject();
|
||||||
|
cur.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSignatureConfig(SignatureConfig config) throws IOException {
|
||||||
|
if (plainSignature == null) {
|
||||||
|
throw new IllegalStateException("Plain signature not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType == null) {
|
||||||
|
determineContentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signValid = generateImage(true, false);
|
||||||
|
byte[] signInvalid = generateImage(true, true);
|
||||||
|
|
||||||
|
config.setSignatureImageSetupId(getSetupId());
|
||||||
|
config.setSignatureImage(plainPng());
|
||||||
|
config.setSignatureImageValid(signValid);
|
||||||
|
config.setSignatureImageInvalid(signInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parse() {
|
||||||
|
if (signatureShape == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTSignatureLine signatureLine = signatureShape.getSignaturelineArray(0);
|
||||||
|
|
||||||
|
setSetupId(new ClassID(signatureLine.getId()));
|
||||||
|
setAllowComments(signatureLine.isSetAllowcomments() ? STTrueFalse.TRUE.equals(signatureLine.getAllowcomments()) : null);
|
||||||
|
setSuggestedSigner(signatureLine.getSuggestedsigner());
|
||||||
|
setSuggestedSigner2(signatureLine.getSuggestedsigner2());
|
||||||
|
setSuggestedSignerEmail(signatureLine.getSuggestedsigneremail());
|
||||||
|
XmlCursor cur = signatureLine.newCursor();
|
||||||
|
try {
|
||||||
|
// the signinginstructions are actually qualified, but our schema version is too old
|
||||||
|
setSigningInstructions(cur.getAttributeText(new QName(MS_OFFICE_URN, "signinginstructions")));
|
||||||
|
} finally {
|
||||||
|
cur.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected interface AddPictureData {
|
||||||
|
/**
|
||||||
|
* Add picture data to the document
|
||||||
|
* @param imageData the image bytes
|
||||||
|
* @param pictureType the picture type - typically PNG
|
||||||
|
* @return the relation id of the newly add picture
|
||||||
|
*/
|
||||||
|
String addPictureData(byte[] imageData, PictureType pictureType) throws InvalidFormatException;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void setRelationId(CTImageData imageData, String relId);
|
||||||
|
|
||||||
|
protected void add(XmlObject signatureContainer, AddPictureData addPictureData) {
|
||||||
|
byte[] inputImage;
|
||||||
|
try {
|
||||||
|
inputImage = generateImage(false, false);
|
||||||
|
|
||||||
|
CTGroup grp = CTGroup.Factory.newInstance();
|
||||||
|
grp.addNewShape();
|
||||||
|
|
||||||
|
XmlCursor contCur = signatureContainer.newCursor();
|
||||||
|
contCur.toEndToken();
|
||||||
|
XmlCursor otherC = grp.newCursor();
|
||||||
|
otherC.copyXmlContents(contCur);
|
||||||
|
otherC.dispose();
|
||||||
|
contCur.toPrevSibling();
|
||||||
|
signatureShape = (CTShape)contCur.getObject();
|
||||||
|
contCur.dispose();
|
||||||
|
|
||||||
|
signatureShape.setAlt("Microsoft Office Signature Line...");
|
||||||
|
signatureShape.setStyle("width:191.95pt;height:96.05pt");
|
||||||
|
// signatureShape.setStyle("position:absolute;margin-left:100.8pt;margin-top:43.2pt;width:192pt;height:96pt;z-index:1");
|
||||||
|
signatureShape.setType("rect");
|
||||||
|
|
||||||
|
String relationId = addPictureData.addPictureData(inputImage, PictureType.PNG);
|
||||||
|
CTImageData imgData = signatureShape.addNewImagedata();
|
||||||
|
setRelationId(imgData, relationId);
|
||||||
|
imgData.setTitle("");
|
||||||
|
|
||||||
|
CTSignatureLine xsl = signatureShape.addNewSignatureline();
|
||||||
|
if (suggestedSigner != null) {
|
||||||
|
xsl.setSuggestedsigner(suggestedSigner);
|
||||||
|
}
|
||||||
|
if (suggestedSigner2 != null) {
|
||||||
|
xsl.setSuggestedsigner2(suggestedSigner2);
|
||||||
|
}
|
||||||
|
if (suggestedSignerEmail != null) {
|
||||||
|
xsl.setSuggestedsigneremail(suggestedSignerEmail);
|
||||||
|
}
|
||||||
|
if (setupId == null) {
|
||||||
|
setupId = new ClassID("{"+ UUID.randomUUID().toString()+"}");
|
||||||
|
}
|
||||||
|
xsl.setId(setupId.toString());
|
||||||
|
xsl.setAllowcomments(STTrueFalse.T);
|
||||||
|
xsl.setIssignatureline(STTrueFalse.T);
|
||||||
|
xsl.setProvid("{00000000-0000-0000-0000-000000000000}");
|
||||||
|
xsl.setExt(STExt.EDIT);
|
||||||
|
xsl.setSigninginstructionsset(STTrueFalse.T);
|
||||||
|
XmlCursor cur = xsl.newCursor();
|
||||||
|
cur.setAttributeText(new QName(MS_OFFICE_URN, "signinginstructions"), signingInstructions);
|
||||||
|
cur.dispose();
|
||||||
|
} catch (IOException | InvalidFormatException e) {
|
||||||
|
// shouldn't happen ...
|
||||||
|
throw new POIXMLException("Can't generate signature line image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void update() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Word and Excel a regenerating the valid and invalid signature line based on the
|
||||||
|
* plain signature. Both are picky about the input format.
|
||||||
|
* Especially EMF images need to a specific device dimension (dpi)
|
||||||
|
* instead of fiddling around with the input image, we generate/register a bitmap image instead
|
||||||
|
*
|
||||||
|
* @return the converted PNG image
|
||||||
|
*/
|
||||||
|
protected byte[] plainPng() throws IOException {
|
||||||
|
byte[] plain = getPlainSignature();
|
||||||
|
PictureType pictureType;
|
||||||
|
switch (FileMagic.valueOf(plain)) {
|
||||||
|
case PNG:
|
||||||
|
return plain;
|
||||||
|
case BMP:
|
||||||
|
pictureType = PictureType.BMP;
|
||||||
|
break;
|
||||||
|
case EMF:
|
||||||
|
pictureType = PictureType.EMF;
|
||||||
|
break;
|
||||||
|
case GIF:
|
||||||
|
pictureType = PictureType.GIF;
|
||||||
|
break;
|
||||||
|
case JPEG:
|
||||||
|
pictureType = PictureType.JPEG;
|
||||||
|
break;
|
||||||
|
case XML:
|
||||||
|
pictureType = PictureType.SVG;
|
||||||
|
break;
|
||||||
|
case TIFF:
|
||||||
|
pictureType = PictureType.TIFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported picture format");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ImageRenderer rnd = DrawPictureShape.getImageRenderer(null, pictureType.contentType);
|
||||||
|
if (rnd == null) {
|
||||||
|
throw new UnsupportedOperationException(pictureType + " can't be rendered - did you provide poi-scratchpad and its dependencies (batik et al.)");
|
||||||
|
}
|
||||||
|
rnd.loadImage(getPlainSignature(), pictureType.contentType);
|
||||||
|
|
||||||
|
Dimension2D dim = rnd.getDimension();
|
||||||
|
int defaultWidth = 300;
|
||||||
|
int defaultHeight = (int)(defaultWidth * dim.getHeight() / dim.getWidth());
|
||||||
|
BufferedImage bi = new BufferedImage(defaultWidth, defaultHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D gfx = bi.createGraphics();
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
rnd.drawImage(gfx, new Rectangle2D.Double(0, 0, defaultWidth, defaultHeight));
|
||||||
|
gfx.dispose();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(bi, "PNG", bos);
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the image for a signature line
|
||||||
|
* @param caption three lines separated by "\n" - usually something like "First name Last name\nRole\nname of the key"
|
||||||
|
* @param inputImage the plain signature - supported formats are PNG,GIF,JPEG,(SVG),EMF,WMF.
|
||||||
|
* for SVG,EMF,WMF poi-scratchpad needs to be in the class-/modulepath
|
||||||
|
* if {@code null}, the inputImage is not rendered
|
||||||
|
* @param invalidText for invalid signature images, use the given text
|
||||||
|
* @return the signature image in PNG format as byte array
|
||||||
|
*/
|
||||||
|
protected byte[] generateImage(boolean showSignature, boolean showInvalidStamp) throws IOException {
|
||||||
|
BufferedImage bi = new BufferedImage(400, 150, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D gfx = bi.createGraphics();
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
String markX = "X\n";
|
||||||
|
String lineX = (new String(new char[500]).replace("\0", " ")) +"\n";
|
||||||
|
String cap = (getCaption() == null) ? getDefaultCaption() : getCaption();
|
||||||
|
String text = markX+lineX+cap.replaceAll("(?m)^", " ");
|
||||||
|
|
||||||
|
AttributedString as = new AttributedString(text);
|
||||||
|
as.addAttribute(TextAttribute.FAMILY, Font.SANS_SERIF);
|
||||||
|
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, markX.length(), text.indexOf('\n', markX.length()));
|
||||||
|
|
||||||
|
as.addAttribute(TextAttribute.SIZE, 15, 0, markX.length());
|
||||||
|
as.addAttribute(TextAttribute.SIZE, 12, markX.length(), text.length());
|
||||||
|
|
||||||
|
gfx.setColor(Color.BLACK);
|
||||||
|
|
||||||
|
AttributedCharacterIterator chIter = as.getIterator();
|
||||||
|
FontRenderContext frc = gfx.getFontRenderContext();
|
||||||
|
LineBreakMeasurer measurer = new LineBreakMeasurer(chIter, frc);
|
||||||
|
float y = 80, x = 5;
|
||||||
|
for (int lineNr = 0; measurer.getPosition() < chIter.getEndIndex(); lineNr++) {
|
||||||
|
int mpos = measurer.getPosition();
|
||||||
|
int limit = text.indexOf('\n', mpos);
|
||||||
|
limit = (limit == -1) ? text.length() : limit+1;
|
||||||
|
TextLayout textLayout = measurer.nextLayout(bi.getWidth()-10, limit, false);
|
||||||
|
if (lineNr != 1) {
|
||||||
|
y += textLayout.getAscent();
|
||||||
|
}
|
||||||
|
textLayout.draw(gfx, x, y);
|
||||||
|
y += textLayout.getDescent() + textLayout.getLeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSignature && plainSignature != null && contentType != null) {
|
||||||
|
|
||||||
|
ImageRenderer renderer = DrawPictureShape.getImageRenderer(gfx, contentType);
|
||||||
|
|
||||||
|
renderer.loadImage(plainSignature, contentType);
|
||||||
|
|
||||||
|
double targetX = 10;
|
||||||
|
double targetY = 100;
|
||||||
|
double targetWidth = bi.getWidth() - targetX;
|
||||||
|
double targetHeight = targetY - 5;
|
||||||
|
Dimension2D dim = renderer.getDimension();
|
||||||
|
double scale = Math.min(targetWidth / dim.getWidth(), targetHeight / dim.getHeight());
|
||||||
|
double effWidth = dim.getWidth() * scale;
|
||||||
|
double effHeight = dim.getHeight() * scale;
|
||||||
|
|
||||||
|
renderer.drawImage(gfx, new Rectangle2D.Double(targetX + ((bi.getWidth() - effWidth) / 2), targetY - effHeight, effWidth, effHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showInvalidStamp && invalidStamp != null && !invalidStamp.isEmpty()) {
|
||||||
|
gfx.setFont(new Font("Lucida Bright", Font.ITALIC, 60));
|
||||||
|
gfx.rotate(Math.toRadians(-15), bi.getWidth()/2., bi.getHeight()/2.);
|
||||||
|
TextLayout tl = new TextLayout(invalidStamp, gfx.getFont(), gfx.getFontRenderContext());
|
||||||
|
Rectangle2D bounds = tl.getBounds();
|
||||||
|
x = (float)((bi.getWidth()-bounds.getWidth())/2 - bounds.getX());
|
||||||
|
y = (float)((bi.getHeight()-bounds.getHeight())/2 - bounds.getY());
|
||||||
|
Shape outline = tl.getOutline(AffineTransform.getTranslateInstance(x+2, y+1));
|
||||||
|
gfx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
|
||||||
|
gfx.setPaint(Color.RED);
|
||||||
|
gfx.draw(outline);
|
||||||
|
gfx.setPaint(new GradientPaint(0, 0, Color.RED, 30, 20, new Color(128, 128, 255), true));
|
||||||
|
tl.draw(gfx, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx.dispose();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(bi, "PNG", bos);
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void determineContentType() {
|
||||||
|
FileMagic fm = FileMagic.valueOf(plainSignature);
|
||||||
|
switch (fm) {
|
||||||
|
case GIF:
|
||||||
|
contentType = PictureType.GIF.contentType;
|
||||||
|
break;
|
||||||
|
case PNG:
|
||||||
|
contentType = PictureType.PNG.contentType;
|
||||||
|
break;
|
||||||
|
case JPEG:
|
||||||
|
contentType = PictureType.JPEG.contentType;
|
||||||
|
break;
|
||||||
|
case XML:
|
||||||
|
contentType = PictureType.SVG.contentType;
|
||||||
|
break;
|
||||||
|
case EMF:
|
||||||
|
contentType = PictureType.EMF.contentType;
|
||||||
|
break;
|
||||||
|
case WMF:
|
||||||
|
contentType = PictureType.WMF.contentType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("unknown image type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -62,6 +63,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship;
|
||||||
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
|
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
|
||||||
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
|
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
|
||||||
import org.apache.poi.openxml4j.opc.TargetMode;
|
import org.apache.poi.openxml4j.opc.TargetMode;
|
||||||
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
|
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
|
||||||
import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
|
import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
|
||||||
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
|
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
|
||||||
|
@ -256,10 +258,20 @@ public class OOXMLSignatureFacet implements SignatureFacet {
|
||||||
|
|
||||||
SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
|
SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
|
||||||
CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
|
CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
|
||||||
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
|
if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) {
|
||||||
|
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
|
||||||
|
}
|
||||||
|
|
||||||
if (signatureConfig.getSignatureDescription() != null) {
|
String desc = signatureConfig.getSignatureDescription();
|
||||||
ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
|
if (desc != null) {
|
||||||
|
ctSigV1.setSignatureComments(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] image = signatureConfig.getSignatureImage();
|
||||||
|
if (image != null) {
|
||||||
|
ctSigV1.setSetupID(signatureConfig.getSignatureImageSetupId().toString());
|
||||||
|
ctSigV1.setSignatureImage(image);
|
||||||
|
ctSigV1.setSignatureType(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
|
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
|
||||||
|
@ -282,6 +294,27 @@ public class OOXMLSignatureFacet implements SignatureFacet {
|
||||||
|
|
||||||
Reference reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
|
Reference reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
|
||||||
references.add(reference);
|
references.add(reference);
|
||||||
|
|
||||||
|
Base64.Encoder enc = Base64.getEncoder();
|
||||||
|
byte[] imageValid = signatureConfig.getSignatureImageValid();
|
||||||
|
if (imageValid != null) {
|
||||||
|
objectId = "idValidSigLnImg";
|
||||||
|
DOMStructure tn = new DOMStructure(document.createTextNode(enc.encodeToString(imageValid)));
|
||||||
|
objects.add(sigFac.newXMLObject(Collections.singletonList(tn), objectId, null, null));
|
||||||
|
|
||||||
|
reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
|
||||||
|
references.add(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] imageInvalid = signatureConfig.getSignatureImageInvalid();
|
||||||
|
if (imageInvalid != null) {
|
||||||
|
objectId = "idInvalidSigLnImg";
|
||||||
|
DOMStructure tn = new DOMStructure(document.createTextNode(enc.encodeToString(imageInvalid)));
|
||||||
|
objects.add(sigFac.newXMLObject(Collections.singletonList(tn), objectId, null, null));
|
||||||
|
|
||||||
|
reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
|
||||||
|
references.add(reference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getRelationshipReferenceURI(String zipEntryName) {
|
protected static String getRelationshipReferenceURI(String zipEntryName) {
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
package org.apache.poi.xslf.model;
|
package org.apache.poi.xslf.model;
|
||||||
|
|
||||||
|
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
@ -113,7 +115,7 @@ public final class ParagraphPropertyFetcher<T> extends PropertyFetcher<T> {
|
||||||
|
|
||||||
static CTTextParagraphProperties select(XSLFShape shape, int level) throws XmlException {
|
static CTTextParagraphProperties select(XSLFShape shape, int level) throws XmlException {
|
||||||
QName[] lvlProp = { new QName(DML_NS, "lvl" + (level + 1) + "pPr") };
|
QName[] lvlProp = { new QName(DML_NS, "lvl" + (level + 1) + "pPr") };
|
||||||
return shape.selectProperty(
|
return selectProperty(shape.getXmlObject(),
|
||||||
CTTextParagraphProperties.class, ParagraphPropertyFetcher::parse, TX_BODY, LST_STYLE, lvlProp);
|
CTTextParagraphProperties.class, ParagraphPropertyFetcher::parse, TX_BODY, LST_STYLE, lvlProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.apache.poi.xslf.model;
|
package org.apache.poi.xslf.model;
|
||||||
|
|
||||||
|
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
|
||||||
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.DML_NS;
|
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.DML_NS;
|
||||||
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.PML_NS;
|
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.PML_NS;
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ public abstract class TextBodyPropertyFetcher<T> extends PropertyFetcher<T> {
|
||||||
public boolean fetch(XSLFShape shape) {
|
public boolean fetch(XSLFShape shape) {
|
||||||
CTTextBodyProperties props = null;
|
CTTextBodyProperties props = null;
|
||||||
try {
|
try {
|
||||||
props = shape.selectProperty(
|
props = selectProperty(shape.getXmlObject(),
|
||||||
CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
|
CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
|
||||||
return (props != null) && fetch(props);
|
return (props != null) && fetch(props);
|
||||||
} catch (XmlException e) {
|
} catch (XmlException e) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import javax.xml.stream.XMLStreamReader;
|
||||||
import org.apache.poi.hpsf.ClassID;
|
import org.apache.poi.hpsf.ClassID;
|
||||||
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
|
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
|
||||||
import org.apache.poi.ooxml.POIXMLException;
|
import org.apache.poi.ooxml.POIXMLException;
|
||||||
|
import org.apache.poi.ooxml.util.XPathHelper;
|
||||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||||
|
@ -76,7 +77,7 @@ public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape<XSL
|
||||||
// select oleObj potentially under AlternateContent
|
// select oleObj potentially under AlternateContent
|
||||||
// usually the mc:Choice element will be selected first
|
// usually the mc:Choice element will be selected first
|
||||||
try {
|
try {
|
||||||
_oleObject = selectProperty(CTOleObject.class, null, GRAPHIC, GRAPHIC_DATA, OLE_OBJ);
|
_oleObject = XPathHelper.selectProperty(getXmlObject(), CTOleObject.class, null, GRAPHIC, GRAPHIC_DATA, OLE_OBJ);
|
||||||
} catch (XmlException e) {
|
} catch (XmlException e) {
|
||||||
// ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
|
// ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
|
@ -146,8 +147,8 @@ public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape<XSL
|
||||||
|
|
||||||
protected CTBlipFillProperties getBlipFill() {
|
protected CTBlipFillProperties getBlipFill() {
|
||||||
try {
|
try {
|
||||||
CTPicture pic = selectProperty
|
CTPicture pic = XPathHelper.selectProperty
|
||||||
(CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
|
(getXmlObject(), CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
|
||||||
return (pic != null) ? pic.getBlipFill() : null;
|
return (pic != null) ? pic.getBlipFill() : null;
|
||||||
} catch (XmlException e) {
|
} catch (XmlException e) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -35,6 +35,7 @@ import javax.imageio.ImageIO;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
import javax.xml.stream.XMLStreamReader;
|
import javax.xml.stream.XMLStreamReader;
|
||||||
|
|
||||||
|
import org.apache.poi.ooxml.util.XPathHelper;
|
||||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||||
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
||||||
import org.apache.poi.sl.usermodel.PictureData;
|
import org.apache.poi.sl.usermodel.PictureData;
|
||||||
|
@ -175,7 +176,7 @@ public class XSLFPictureShape extends XSLFSimpleShape
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return selectProperty(CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
|
return XPathHelper.selectProperty(getXmlObject(), CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
|
||||||
} catch (XmlException xe) {
|
} catch (XmlException xe) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.apache.poi.xslf.usermodel;
|
package org.apache.poi.xslf.usermodel;
|
||||||
|
|
||||||
|
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
|
||||||
import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS;
|
import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -220,7 +221,7 @@ public class XSLFPlaceholderDetails implements PlaceholderDetails {
|
||||||
|
|
||||||
private CTApplicationNonVisualDrawingProps getNvProps() {
|
private CTApplicationNonVisualDrawingProps getNvProps() {
|
||||||
try {
|
try {
|
||||||
return shape.selectProperty(CTApplicationNonVisualDrawingProps.class, null, NV_CONTAINER, NV_PROPS);
|
return selectProperty(shape.getXmlObject(), CTApplicationNonVisualDrawingProps.class, null, NV_CONTAINER, NV_PROPS);
|
||||||
} catch (XmlException e) {
|
} catch (XmlException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,11 @@ package org.apache.poi.xslf.usermodel;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
import javax.xml.stream.XMLStreamReader;
|
import javax.xml.stream.XMLStreamReader;
|
||||||
|
|
||||||
import com.microsoft.schemas.compatibility.AlternateContentDocument;
|
import org.apache.poi.ooxml.util.XPathHelper;
|
||||||
import com.microsoft.schemas.compatibility.AlternateContentDocument.AlternateContent;
|
|
||||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||||
import org.apache.poi.sl.draw.DrawFactory;
|
import org.apache.poi.sl.draw.DrawFactory;
|
||||||
import org.apache.poi.sl.draw.DrawPaint;
|
import org.apache.poi.sl.draw.DrawPaint;
|
||||||
|
@ -45,7 +43,6 @@ import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties;
|
||||||
import org.apache.xmlbeans.XmlCursor;
|
import org.apache.xmlbeans.XmlCursor;
|
||||||
import org.apache.xmlbeans.XmlException;
|
import org.apache.xmlbeans.XmlException;
|
||||||
import org.apache.xmlbeans.XmlObject;
|
import org.apache.xmlbeans.XmlObject;
|
||||||
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
|
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
|
||||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
|
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
|
||||||
|
@ -76,10 +73,6 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
||||||
|
|
||||||
static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||||
static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
|
static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
|
||||||
private static final String MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
||||||
private static final String MAC_DML_NS = "http://schemas.microsoft.com/office/mac/drawingml/2008/main";
|
|
||||||
|
|
||||||
private static final QName ALTERNATE_CONTENT_TAG = new QName(MC_NS, "AlternateContent");
|
|
||||||
|
|
||||||
private static final QName[] NV_CONTAINER = {
|
private static final QName[] NV_CONTAINER = {
|
||||||
new QName(PML_NS, "nvSpPr"),
|
new QName(PML_NS, "nvSpPr"),
|
||||||
|
@ -93,12 +86,6 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
||||||
new QName(PML_NS, "cNvPr")
|
new QName(PML_NS, "cNvPr")
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String OSGI_ERROR =
|
|
||||||
"Schemas (*.xsb) for <CLASS> can't be loaded - usually this happens when OSGI " +
|
|
||||||
"loading is used and the thread context classloader has no reference to " +
|
|
||||||
"the xmlbeans classes - please either verify if the <XSB>.xsb is on the " +
|
|
||||||
"classpath or alternatively try to use the full ooxml-schemas-x.x.jar";
|
|
||||||
|
|
||||||
private final XmlObject _shape;
|
private final XmlObject _shape;
|
||||||
private final XSLFSheet _sheet;
|
private final XSLFSheet _sheet;
|
||||||
private XSLFShapeContainer _parent;
|
private XSLFShapeContainer _parent;
|
||||||
|
@ -239,7 +226,7 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
||||||
protected CTNonVisualDrawingProps getCNvPr() {
|
protected CTNonVisualDrawingProps getCNvPr() {
|
||||||
try {
|
try {
|
||||||
if (_nvPr == null) {
|
if (_nvPr == null) {
|
||||||
_nvPr = selectProperty(CTNonVisualDrawingProps.class, null, NV_CONTAINER, CNV_PROPS);
|
_nvPr = XPathHelper.selectProperty(getXmlObject(), CTNonVisualDrawingProps.class, null, NV_CONTAINER, CNV_PROPS);
|
||||||
}
|
}
|
||||||
return _nvPr;
|
return _nvPr;
|
||||||
} catch (XmlException e) {
|
} catch (XmlException e) {
|
||||||
|
@ -322,160 +309,6 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
||||||
return (resultClass.isInstance(rs[0])) ? (T)rs[0] : null;
|
return (resultClass.isInstance(rs[0])) ? (T)rs[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal code - API may change any time!
|
|
||||||
* <p>
|
|
||||||
* The {@link #selectProperty(Class, String)} xquery method has some performance penalties,
|
|
||||||
* which can be workaround by using {@link XmlCursor}. This method also takes into account
|
|
||||||
* that {@code AlternateContent} tags can occur anywhere on the given path.
|
|
||||||
* <p>
|
|
||||||
* It returns the first element found - the search order is:
|
|
||||||
* <ul>
|
|
||||||
* <li>searching for a direct child</li>
|
|
||||||
* <li>searching for a AlternateContent.Choice child</li>
|
|
||||||
* <li>searching for a AlternateContent.Fallback child</li>
|
|
||||||
* </ul>
|
|
||||||
* Currently POI OOXML is based on the first edition of the ECMA 376 schema, which doesn't
|
|
||||||
* allow AlternateContent tags to show up everywhere. The factory flag is
|
|
||||||
* a workaround to process files based on a later edition. But it comes with the drawback:
|
|
||||||
* any change on the returned XmlObject aren't saved back to the underlying document -
|
|
||||||
* so it's a non updatable clone. If factory is null, a XmlException is
|
|
||||||
* thrown if the AlternateContent is not allowed by the surrounding element or if the
|
|
||||||
* extracted object is of the generic type XmlAnyTypeImpl.
|
|
||||||
*
|
|
||||||
* @param resultClass the requested result class
|
|
||||||
* @param factory a factory parse method reference to allow reparsing of elements
|
|
||||||
* extracted from AlternateContent elements. Usually the enclosing XmlBeans type needs to be used
|
|
||||||
* to parse the stream
|
|
||||||
* @param path the elements path, each array must contain at least 1 QName,
|
|
||||||
* but can contain additional alternative tags
|
|
||||||
* @return the xml object at the path location, or null if not found
|
|
||||||
*
|
|
||||||
* @throws XmlException If factory is null, a XmlException is
|
|
||||||
* thrown if the AlternateContent is not allowed by the surrounding element or if the
|
|
||||||
* extracted object is of the generic type XmlAnyTypeImpl.
|
|
||||||
*
|
|
||||||
* @since POI 4.1.2
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Internal
|
|
||||||
public <T extends XmlObject> T selectProperty(Class<T> resultClass, ReparseFactory<T> factory, QName[]... path)
|
|
||||||
throws XmlException {
|
|
||||||
XmlObject xo = getXmlObject();
|
|
||||||
XmlCursor cur = xo.newCursor();
|
|
||||||
XmlCursor innerCur = null;
|
|
||||||
try {
|
|
||||||
innerCur = selectProperty(cur, path, 0, factory != null, false);
|
|
||||||
if (innerCur == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pesky XmlBeans bug - see Bugzilla #49934
|
|
||||||
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
|
|
||||||
xo = innerCur.getObject();
|
|
||||||
if (xo instanceof XmlAnyTypeImpl) {
|
|
||||||
String errorTxt = OSGI_ERROR
|
|
||||||
.replace("<CLASS>", resultClass.getSimpleName())
|
|
||||||
.replace("<XSB>", resultClass.getSimpleName().toLowerCase(Locale.ROOT)+"*");
|
|
||||||
if (factory == null) {
|
|
||||||
throw new XmlException(errorTxt);
|
|
||||||
} else {
|
|
||||||
xo = factory.parse(innerCur.newXMLStreamReader());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (T)xo;
|
|
||||||
} finally {
|
|
||||||
cur.dispose();
|
|
||||||
if (innerCur != null) {
|
|
||||||
innerCur.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private XmlCursor selectProperty(final XmlCursor cur, final QName[][] path, final int offset, final boolean reparseAlternate, final boolean isAlternate)
|
|
||||||
throws XmlException {
|
|
||||||
// first try the direct children
|
|
||||||
for (QName qn : path[offset]) {
|
|
||||||
if (cur.toChild(qn)) {
|
|
||||||
if (offset == path.length-1) {
|
|
||||||
return cur;
|
|
||||||
}
|
|
||||||
cur.push();
|
|
||||||
XmlCursor innerCur = selectProperty(cur, path, offset+1, reparseAlternate, false);
|
|
||||||
if (innerCur != null) {
|
|
||||||
return innerCur;
|
|
||||||
}
|
|
||||||
cur.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we were called inside an alternate content handling don't look for alternates again
|
|
||||||
if (isAlternate || !cur.toChild(ALTERNATE_CONTENT_TAG)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise check first the choice then the fallback content
|
|
||||||
XmlObject xo = cur.getObject();
|
|
||||||
AlternateContent alterCont;
|
|
||||||
if (xo instanceof AlternateContent) {
|
|
||||||
alterCont = (AlternateContent)xo;
|
|
||||||
} else {
|
|
||||||
// Pesky XmlBeans bug - see Bugzilla #49934
|
|
||||||
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
|
|
||||||
if (!reparseAlternate) {
|
|
||||||
throw new XmlException(OSGI_ERROR
|
|
||||||
.replace("<CLASS>", "AlternateContent")
|
|
||||||
.replace("<XSB>", "alternatecontentelement")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
AlternateContentDocument acd = AlternateContentDocument.Factory.parse(cur.newXMLStreamReader());
|
|
||||||
alterCont = acd.getAlternateContent();
|
|
||||||
} catch (XmlException e) {
|
|
||||||
throw new XmlException("unable to parse AlternateContent element", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final int choices = alterCont.sizeOfChoiceArray();
|
|
||||||
for (int i=0; i<choices; i++) {
|
|
||||||
// TODO: check [Requires] attribute of [Choice] element, if we can handle the content
|
|
||||||
AlternateContent.Choice choice = alterCont.getChoiceArray(i);
|
|
||||||
XmlCursor cCur = choice.newCursor();
|
|
||||||
XmlCursor innerCur = null;
|
|
||||||
try {
|
|
||||||
String requiresNS = cCur.namespaceForPrefix(choice.getRequires());
|
|
||||||
if (MAC_DML_NS.equalsIgnoreCase(requiresNS)) {
|
|
||||||
// Mac DML usually contains PDFs ...
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
innerCur = selectProperty(cCur, path, offset, reparseAlternate, true);
|
|
||||||
if (innerCur != null) {
|
|
||||||
return innerCur;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (innerCur != cCur) {
|
|
||||||
cCur.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alterCont.isSetFallback()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
XmlCursor fCur = alterCont.getFallback().newCursor();
|
|
||||||
XmlCursor innerCur = null;
|
|
||||||
try {
|
|
||||||
innerCur = selectProperty(fCur, path, offset, reparseAlternate, true);
|
|
||||||
return innerCur;
|
|
||||||
} finally {
|
|
||||||
if (innerCur != fCur) {
|
|
||||||
fCur.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walk up the inheritance tree and fetch shape properties.<p>
|
* Walk up the inheritance tree and fetch shape properties.<p>
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
package org.apache.poi.xssf.usermodel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import com.microsoft.schemas.office.excel.CTClientData;
|
||||||
|
import com.microsoft.schemas.office.excel.STCF;
|
||||||
|
import com.microsoft.schemas.office.excel.STObjectType;
|
||||||
|
import com.microsoft.schemas.office.excel.STTrueFalseBlank;
|
||||||
|
import com.microsoft.schemas.office.office.CTSignatureLine;
|
||||||
|
import com.microsoft.schemas.vml.CTImageData;
|
||||||
|
import com.microsoft.schemas.vml.CTShape;
|
||||||
|
import org.apache.poi.common.usermodel.PictureType;
|
||||||
|
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||||
|
import org.apache.poi.ooxml.POIXMLException;
|
||||||
|
import org.apache.poi.ooxml.POIXMLRelation;
|
||||||
|
import org.apache.poi.ooxml.util.XPathHelper;
|
||||||
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
import org.apache.poi.poifs.crypt.dsig.SignatureLine;
|
||||||
|
import org.apache.poi.schemas.vmldrawing.CTXML;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
|
||||||
|
public class XSSFSignatureLine extends SignatureLine {
|
||||||
|
private static final String MS_VML_URN = "urn:schemas-microsoft-com:vml";
|
||||||
|
|
||||||
|
public void parse(XSSFSheet sheet) throws XmlException {
|
||||||
|
XSSFVMLDrawing vml = sheet.getVMLDrawing(false);
|
||||||
|
if (vml == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CTSignatureLine line = XPathHelper.selectProperty(vml.getDocument(), CTSignatureLine.class, null,
|
||||||
|
new QName[]{XSSFVMLDrawing.QNAME_VMLDRAWING},
|
||||||
|
new QName[]{new QName(MS_VML_URN, "shape")},
|
||||||
|
new QName[]{QNAME_SIGNATURE_LINE});
|
||||||
|
|
||||||
|
if (line != null) {
|
||||||
|
setSignatureShape(line);
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(XSSFSheet sheet, XSSFClientAnchor anchor) {
|
||||||
|
XSSFVMLDrawing vml = sheet.getVMLDrawing(true);
|
||||||
|
CTXML root = vml.getDocument().getXml();
|
||||||
|
add(root, (image, type) -> addPicture(image,type,sheet));
|
||||||
|
CTShape shape = getSignatureShape();
|
||||||
|
CTClientData clientData = shape.addNewClientData();
|
||||||
|
// LeftColumn, LeftOffset, TopRow, TopOffset, RightColumn, RightOffset, BottomRow, BottomOffset
|
||||||
|
String anchorStr =
|
||||||
|
anchor.getCol1()+", "+
|
||||||
|
anchor.getDx1()+", "+
|
||||||
|
anchor.getRow1()+", "+
|
||||||
|
anchor.getDy1()+", "+
|
||||||
|
anchor.getCol2()+", "+
|
||||||
|
anchor.getDx2()+", "+
|
||||||
|
anchor.getRow2()+", "+
|
||||||
|
anchor.getDy2();
|
||||||
|
// anchorStr = "2, 0, 3, 0, 5, 136, 9, 32";
|
||||||
|
clientData.addAnchor(anchorStr);
|
||||||
|
clientData.setObjectType(STObjectType.PICT);
|
||||||
|
clientData.addSizeWithCells(STTrueFalseBlank.X);
|
||||||
|
clientData.addCF(STCF.PICT);
|
||||||
|
clientData.addAutoPict(STTrueFalseBlank.X);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setRelationId(CTImageData imageData, String relId) {
|
||||||
|
imageData.setRelid(relId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String addPicture(byte[] image, PictureType type, XSSFSheet sheet) throws InvalidFormatException {
|
||||||
|
XSSFWorkbook wb = sheet.getWorkbook();
|
||||||
|
XSSFVMLDrawing vml = sheet.getVMLDrawing(false);
|
||||||
|
POIXMLRelation xtype = mapType(type);
|
||||||
|
int idx = wb.getNextPartNumber(xtype, -1);
|
||||||
|
POIXMLDocumentPart.RelationPart rp = vml.createRelationship(xtype, XSSFFactory.getInstance(), idx, false);
|
||||||
|
POIXMLDocumentPart dp = rp.getDocumentPart();
|
||||||
|
try (OutputStream out = dp.getPackagePart().getOutputStream()) {
|
||||||
|
out.write(image);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new POIXMLException(e);
|
||||||
|
}
|
||||||
|
return rp.getRelationship().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static POIXMLRelation mapType(PictureType type) throws InvalidFormatException {
|
||||||
|
switch (type) {
|
||||||
|
case BMP:
|
||||||
|
return XSSFRelation.IMAGE_BMP;
|
||||||
|
case DIB:
|
||||||
|
return XSSFRelation.IMAGE_DIB;
|
||||||
|
case EMF:
|
||||||
|
return XSSFRelation.IMAGE_EMF;
|
||||||
|
case EPS:
|
||||||
|
return XSSFRelation.IMAGE_EPS;
|
||||||
|
case GIF:
|
||||||
|
return XSSFRelation.IMAGE_GIF;
|
||||||
|
case JPEG:
|
||||||
|
return XSSFRelation.IMAGE_JPEG;
|
||||||
|
case PICT:
|
||||||
|
return XSSFRelation.IMAGE_PICT;
|
||||||
|
case PNG:
|
||||||
|
return XSSFRelation.IMAGE_PNG;
|
||||||
|
case TIFF:
|
||||||
|
return XSSFRelation.IMAGE_TIFF;
|
||||||
|
case WMF:
|
||||||
|
return XSSFRelation.IMAGE_WMF;
|
||||||
|
case WPG:
|
||||||
|
return XSSFRelation.IMAGE_WPG;
|
||||||
|
default:
|
||||||
|
throw new InvalidFormatException("Unsupported picture format "+type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,33 +22,23 @@ import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.StringReader;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
|
||||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
|
||||||
import org.apache.poi.ooxml.util.DocumentHelper;
|
|
||||||
import org.apache.poi.util.ReplacingInputStream;
|
|
||||||
import org.apache.xmlbeans.XmlCursor;
|
|
||||||
import org.apache.xmlbeans.XmlException;
|
|
||||||
import org.apache.xmlbeans.XmlObject;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
import com.microsoft.schemas.office.excel.CTClientData;
|
import com.microsoft.schemas.office.excel.CTClientData;
|
||||||
import com.microsoft.schemas.office.excel.STObjectType;
|
import com.microsoft.schemas.office.excel.STObjectType;
|
||||||
import com.microsoft.schemas.office.office.CTIdMap;
|
import com.microsoft.schemas.office.office.CTIdMap;
|
||||||
import com.microsoft.schemas.office.office.CTShapeLayout;
|
import com.microsoft.schemas.office.office.CTShapeLayout;
|
||||||
import com.microsoft.schemas.office.office.STConnectType;
|
import com.microsoft.schemas.office.office.STConnectType;
|
||||||
import com.microsoft.schemas.office.office.STInsetMode;
|
import com.microsoft.schemas.office.office.STInsetMode;
|
||||||
|
import com.microsoft.schemas.office.office.ShapelayoutDocument;
|
||||||
|
import com.microsoft.schemas.vml.CTGroup;
|
||||||
import com.microsoft.schemas.vml.CTPath;
|
import com.microsoft.schemas.vml.CTPath;
|
||||||
import com.microsoft.schemas.vml.CTShadow;
|
import com.microsoft.schemas.vml.CTShadow;
|
||||||
import com.microsoft.schemas.vml.CTShape;
|
import com.microsoft.schemas.vml.CTShape;
|
||||||
|
@ -56,6 +46,17 @@ import com.microsoft.schemas.vml.CTShapetype;
|
||||||
import com.microsoft.schemas.vml.STExt;
|
import com.microsoft.schemas.vml.STExt;
|
||||||
import com.microsoft.schemas.vml.STStrokeJoinStyle;
|
import com.microsoft.schemas.vml.STStrokeJoinStyle;
|
||||||
import com.microsoft.schemas.vml.STTrueFalse;
|
import com.microsoft.schemas.vml.STTrueFalse;
|
||||||
|
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||||
|
import org.apache.poi.ooxml.util.DocumentHelper;
|
||||||
|
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||||
|
import org.apache.poi.schemas.vmldrawing.XmlDocument;
|
||||||
|
import org.apache.poi.util.ReplacingInputStream;
|
||||||
|
import org.apache.xmlbeans.XmlCursor;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
import org.apache.xmlbeans.XmlObject;
|
||||||
|
import org.apache.xmlbeans.XmlOptions;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a SpreadsheetML VML drawing.
|
* Represents a SpreadsheetML VML drawing.
|
||||||
|
@ -71,29 +72,31 @@ import com.microsoft.schemas.vml.STTrueFalse;
|
||||||
* considered a deprecated format included in Office Open XML for legacy reasons only and new applications that
|
* considered a deprecated format included in Office Open XML for legacy reasons only and new applications that
|
||||||
* need a file format for drawings are strongly encouraged to use preferentially DrawingML
|
* need a file format for drawings are strongly encouraged to use preferentially DrawingML
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Warning - Excel is known to put invalid XML into these files!
|
* Warning - Excel is known to put invalid XML into these files!
|
||||||
* For example, >br< without being closed or escaped crops up.
|
* For example, >br< without being closed or escaped crops up.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* See 6.4 VML - SpreadsheetML Drawing in Office Open XML Part 4 - Markup Language Reference.pdf
|
* See 6.4 VML - SpreadsheetML Drawing in Office Open XML Part 4 - Markup Language Reference.pdf
|
||||||
*
|
|
||||||
* @author Yegor Kozlov
|
|
||||||
*/
|
*/
|
||||||
public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
private static final QName QNAME_SHAPE_LAYOUT = new QName("urn:schemas-microsoft-com:office:office", "shapelayout");
|
// this ID value seems to have significance to Excel >= 2010;
|
||||||
private static final QName QNAME_SHAPE_TYPE = new QName("urn:schemas-microsoft-com:vml", "shapetype");
|
// see https://issues.apache.org/bugzilla/show_bug.cgi?id=55409
|
||||||
private static final QName QNAME_SHAPE = new QName("urn:schemas-microsoft-com:vml", "shape");
|
private static final String COMMENT_SHAPE_TYPE_ID = "_x0000_t202";
|
||||||
private static final String COMMENT_SHAPE_TYPE_ID = "_x0000_t202"; // this ID value seems to have significance to Excel >= 2010; see https://issues.apache.org/bugzilla/show_bug.cgi?id=55409
|
|
||||||
|
/**
|
||||||
|
* to actually process the namespace-less vmldrawing, we've introduced a proxy namespace.
|
||||||
|
* this namespace is active in-memory, but will be removed on saving to the file
|
||||||
|
*/
|
||||||
|
public static final QName QNAME_VMLDRAWING = new QName("urn:schemas-poi-apache-org:vmldrawing", "xml");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* regexp to parse shape ids, in VML they have weird form of id="_x0000_s1026"
|
* regexp to parse shape ids, in VML they have weird form of id="_x0000_s1026"
|
||||||
*/
|
*/
|
||||||
private static final Pattern ptrn_shapeId = Pattern.compile("_x0000_s(\\d+)");
|
private static final Pattern ptrn_shapeId = Pattern.compile("_x0000_s(\\d+)");
|
||||||
|
|
||||||
private List<QName> _qnames = new ArrayList<>();
|
private XmlDocument root;
|
||||||
private List<XmlObject> _items = new ArrayList<>();
|
|
||||||
private String _shapeTypeId;
|
private String _shapeTypeId;
|
||||||
private int _shapeId = 1024;
|
private int _shapeId = 1024;
|
||||||
|
|
||||||
|
@ -112,7 +115,7 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
*
|
*
|
||||||
* @param part the package part holding the drawing data,
|
* @param part the package part holding the drawing data,
|
||||||
* the content type must be <code>application/vnd.openxmlformats-officedocument.drawing+xml</code>
|
* the content type must be <code>application/vnd.openxmlformats-officedocument.drawing+xml</code>
|
||||||
*
|
*
|
||||||
* @since POI 3.14-Beta1
|
* @since POI 3.14-Beta1
|
||||||
*/
|
*/
|
||||||
protected XSSFVMLDrawing(PackagePart part) throws IOException, XmlException {
|
protected XSSFVMLDrawing(PackagePart part) throws IOException, XmlException {
|
||||||
|
@ -120,6 +123,11 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
read(getPackagePart().getInputStream());
|
read(getPackagePart().getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XmlDocument getDocument() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected void read(InputStream is) throws IOException, XmlException {
|
protected void read(InputStream is) throws IOException, XmlException {
|
||||||
Document doc;
|
Document doc;
|
||||||
try {
|
try {
|
||||||
|
@ -133,92 +141,84 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
} catch (SAXException e) {
|
} catch (SAXException e) {
|
||||||
throw new XmlException(e.getMessage(), e);
|
throw new XmlException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
XmlObject root = XmlObject.Factory.parse(doc, DEFAULT_XML_OPTIONS);
|
|
||||||
|
|
||||||
_qnames = new ArrayList<>();
|
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||||
_items = new ArrayList<>();
|
xopt.setLoadSubstituteNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||||
for(XmlObject obj : root.selectPath("$this/xml/*")) {
|
|
||||||
Node nd = obj.getDomNode();
|
root = XmlDocument.Factory.parse(doc, xopt);
|
||||||
QName qname = new QName(nd.getNamespaceURI(), nd.getLocalName());
|
XmlCursor cur = root.getXml().newCursor();
|
||||||
if (qname.equals(QNAME_SHAPE_LAYOUT)) {
|
|
||||||
_items.add(CTShapeLayout.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS));
|
try {
|
||||||
} else if (qname.equals(QNAME_SHAPE_TYPE)) {
|
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||||
CTShapetype st = CTShapetype.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS);
|
XmlObject xo = cur.getObject();
|
||||||
_items.add(st);
|
if (xo instanceof CTShapetype) {
|
||||||
_shapeTypeId = st.getId();
|
_shapeTypeId = ((CTShapetype)xo).getId();
|
||||||
} else if (qname.equals(QNAME_SHAPE)) {
|
} else if (xo instanceof CTShape) {
|
||||||
CTShape shape = CTShape.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS);
|
CTShape shape = (CTShape)xo;
|
||||||
String id = shape.getId();
|
String id = shape.getId();
|
||||||
if(id != null) {
|
if(id != null) {
|
||||||
Matcher m = ptrn_shapeId.matcher(id);
|
Matcher m = ptrn_shapeId.matcher(id);
|
||||||
if(m.find()) {
|
if(m.find()) {
|
||||||
_shapeId = Math.max(_shapeId, Integer.parseInt(m.group(1)));
|
_shapeId = Math.max(_shapeId, Integer.parseInt(m.group(1)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_items.add(shape);
|
|
||||||
} else {
|
|
||||||
Document doc2;
|
|
||||||
try {
|
|
||||||
InputSource is2 = new InputSource(new StringReader(obj.xmlText()));
|
|
||||||
doc2 = DocumentHelper.readDocument(is2);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
throw new XmlException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_items.add(XmlObject.Factory.parse(doc2, DEFAULT_XML_OPTIONS));
|
|
||||||
}
|
}
|
||||||
_qnames.add(qname);
|
} finally {
|
||||||
|
cur.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<XmlObject> getItems(){
|
protected List<XmlObject> getItems(){
|
||||||
return _items;
|
List<XmlObject> items = new ArrayList<>();
|
||||||
|
|
||||||
|
XmlCursor cur = root.getXml().newCursor();
|
||||||
|
try {
|
||||||
|
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||||
|
items.add(cur.getObject());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cur.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void write(OutputStream out) throws IOException {
|
protected void write(OutputStream out) throws IOException {
|
||||||
XmlObject rootObject = XmlObject.Factory.newInstance();
|
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||||
XmlCursor rootCursor = rootObject.newCursor();
|
xopt.setSaveImplicitNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||||
rootCursor.toNextToken();
|
root.save(out, xopt);
|
||||||
rootCursor.beginElement("xml");
|
|
||||||
|
|
||||||
for(int i=0; i < _items.size(); i++){
|
|
||||||
XmlCursor xc = _items.get(i).newCursor();
|
|
||||||
rootCursor.beginElement(_qnames.get(i));
|
|
||||||
while(xc.toNextToken() == XmlCursor.TokenType.ATTR) {
|
|
||||||
Node anode = xc.getDomNode();
|
|
||||||
rootCursor.insertAttributeWithValue(anode.getLocalName(), anode.getNamespaceURI(), anode.getNodeValue());
|
|
||||||
}
|
|
||||||
xc.toStartDoc();
|
|
||||||
xc.copyXmlContents(rootCursor);
|
|
||||||
rootCursor.toNextToken();
|
|
||||||
xc.dispose();
|
|
||||||
}
|
|
||||||
rootCursor.dispose();
|
|
||||||
|
|
||||||
rootObject.save(out, DEFAULT_XML_OPTIONS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void commit() throws IOException {
|
protected void commit() throws IOException {
|
||||||
PackagePart part = getPackagePart();
|
PackagePart part = getPackagePart();
|
||||||
OutputStream out = part.getOutputStream();
|
try (OutputStream out = part.getOutputStream()) {
|
||||||
write(out);
|
write(out);
|
||||||
out.close();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new Speadsheet VML drawing
|
* Initialize a new Speadsheet VML drawing
|
||||||
*/
|
*/
|
||||||
private void newDrawing(){
|
private void newDrawing(){
|
||||||
CTShapeLayout layout = CTShapeLayout.Factory.newInstance();
|
root = XmlDocument.Factory.newInstance();
|
||||||
|
XmlCursor xml = root.addNewXml().newCursor();
|
||||||
|
|
||||||
|
ShapelayoutDocument layDoc = ShapelayoutDocument.Factory.newInstance();
|
||||||
|
CTShapeLayout layout = layDoc.addNewShapelayout();
|
||||||
layout.setExt(STExt.EDIT);
|
layout.setExt(STExt.EDIT);
|
||||||
CTIdMap idmap = layout.addNewIdmap();
|
CTIdMap idmap = layout.addNewIdmap();
|
||||||
idmap.setExt(STExt.EDIT);
|
idmap.setExt(STExt.EDIT);
|
||||||
idmap.setData("1");
|
idmap.setData("1");
|
||||||
_items.add(layout);
|
|
||||||
_qnames.add(QNAME_SHAPE_LAYOUT);
|
|
||||||
|
|
||||||
CTShapetype shapetype = CTShapetype.Factory.newInstance();
|
xml.toEndToken();
|
||||||
|
XmlCursor layCur = layDoc.newCursor();
|
||||||
|
layCur.copyXmlContents(xml);
|
||||||
|
layCur.dispose();
|
||||||
|
|
||||||
|
CTGroup grp = CTGroup.Factory.newInstance();
|
||||||
|
CTShapetype shapetype = grp.addNewShapetype();
|
||||||
_shapeTypeId = COMMENT_SHAPE_TYPE_ID;
|
_shapeTypeId = COMMENT_SHAPE_TYPE_ID;
|
||||||
shapetype.setId(_shapeTypeId);
|
shapetype.setId(_shapeTypeId);
|
||||||
shapetype.setCoordsize("21600,21600");
|
shapetype.setCoordsize("21600,21600");
|
||||||
|
@ -228,12 +228,17 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
CTPath path = shapetype.addNewPath();
|
CTPath path = shapetype.addNewPath();
|
||||||
path.setGradientshapeok(STTrueFalse.T);
|
path.setGradientshapeok(STTrueFalse.T);
|
||||||
path.setConnecttype(STConnectType.RECT);
|
path.setConnecttype(STConnectType.RECT);
|
||||||
_items.add(shapetype);
|
|
||||||
_qnames.add(QNAME_SHAPE_TYPE);
|
xml.toEndToken();
|
||||||
|
XmlCursor grpCur = grp.newCursor();
|
||||||
|
grpCur.copyXmlContents(xml);
|
||||||
|
grpCur.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CTShape newCommentShape(){
|
protected CTShape newCommentShape(){
|
||||||
CTShape shape = CTShape.Factory.newInstance();
|
CTGroup grp = CTGroup.Factory.newInstance();
|
||||||
|
|
||||||
|
CTShape shape = grp.addNewShape();
|
||||||
shape.setId("_x0000_s" + (++_shapeId));
|
shape.setId("_x0000_s" + (++_shapeId));
|
||||||
shape.setType("#" + _shapeTypeId);
|
shape.setType("#" + _shapeTypeId);
|
||||||
shape.setStyle("position:absolute; visibility:hidden");
|
shape.setStyle("position:absolute; visibility:hidden");
|
||||||
|
@ -254,8 +259,16 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
cldata.addNewAutoFill().setStringValue("False");
|
cldata.addNewAutoFill().setStringValue("False");
|
||||||
cldata.addNewRow().setBigIntegerValue(BigInteger.valueOf(0));
|
cldata.addNewRow().setBigIntegerValue(BigInteger.valueOf(0));
|
||||||
cldata.addNewColumn().setBigIntegerValue(BigInteger.valueOf(0));
|
cldata.addNewColumn().setBigIntegerValue(BigInteger.valueOf(0));
|
||||||
_items.add(shape);
|
|
||||||
_qnames.add(QNAME_SHAPE);
|
XmlCursor xml = root.getXml().newCursor();
|
||||||
|
xml.toEndToken();
|
||||||
|
XmlCursor grpCur = grp.newCursor();
|
||||||
|
grpCur.copyXmlContents(xml);
|
||||||
|
xml.toPrevSibling();
|
||||||
|
shape = (CTShape)xml.getObject();
|
||||||
|
grpCur.dispose();
|
||||||
|
xml.dispose();
|
||||||
|
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,26 +278,45 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
||||||
* @return the comment shape or <code>null</code>
|
* @return the comment shape or <code>null</code>
|
||||||
*/
|
*/
|
||||||
public CTShape findCommentShape(int row, int col){
|
public CTShape findCommentShape(int row, int col){
|
||||||
for(XmlObject itm : _items){
|
XmlCursor cur = root.getXml().newCursor();
|
||||||
if(itm instanceof CTShape){
|
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||||
CTShape sh = (CTShape)itm;
|
XmlObject itm = cur.getObject();
|
||||||
if(sh.sizeOfClientDataArray() > 0){
|
if (matchCommentShape(itm, row, col)) {
|
||||||
CTClientData cldata = sh.getClientDataArray(0);
|
return (CTShape)itm;
|
||||||
if(cldata.getObjectType() == STObjectType.NOTE){
|
|
||||||
int crow = cldata.getRowArray(0).intValue();
|
|
||||||
int ccol = cldata.getColumnArray(0).intValue();
|
|
||||||
if(crow == row && ccol == col) {
|
|
||||||
return sh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean matchCommentShape(XmlObject itm, int row, int col) {
|
||||||
|
if (!(itm instanceof CTShape)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTShape sh = (CTShape)itm;
|
||||||
|
if (sh.sizeOfClientDataArray() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTClientData cldata = sh.getClientDataArray(0);
|
||||||
|
if(cldata.getObjectType() != STObjectType.NOTE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crow = cldata.getRowArray(0).intValue();
|
||||||
|
int ccol = cldata.getColumnArray(0).intValue();
|
||||||
|
return (crow == row && ccol == col);
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean removeCommentShape(int row, int col){
|
protected boolean removeCommentShape(int row, int col){
|
||||||
CTShape shape = findCommentShape(row, col);
|
XmlCursor cur = root.getXml().newCursor();
|
||||||
return shape != null && _items.remove(shape);
|
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||||
|
XmlObject itm = cur.getObject();
|
||||||
|
if (matchCommentShape(itm, row, col)) {
|
||||||
|
cur.removeXml();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.xwpf.usermodel;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import com.microsoft.schemas.office.office.CTSignatureLine;
|
||||||
|
import com.microsoft.schemas.vml.CTImageData;
|
||||||
|
import org.apache.poi.common.usermodel.PictureType;
|
||||||
|
import org.apache.poi.ooxml.util.XPathHelper;
|
||||||
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
import org.apache.poi.poifs.crypt.dsig.SignatureLine;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPicture;
|
||||||
|
|
||||||
|
public class XWPFSignatureLine extends SignatureLine {
|
||||||
|
static final String NS_OOXML_WP_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
||||||
|
private static final String MS_VML_URN = "urn:schemas-microsoft-com:vml";
|
||||||
|
|
||||||
|
private CTSignatureLine line;
|
||||||
|
|
||||||
|
public void parse(XWPFDocument doc) throws XmlException {
|
||||||
|
line = XPathHelper.selectProperty(doc.getDocument(), CTSignatureLine.class, null,
|
||||||
|
new QName[]{new QName(NS_OOXML_WP_MAIN, "body")},
|
||||||
|
new QName[]{new QName(NS_OOXML_WP_MAIN, "p")},
|
||||||
|
new QName[]{new QName(NS_OOXML_WP_MAIN, "r")},
|
||||||
|
new QName[]{new QName(NS_OOXML_WP_MAIN, "pict")},
|
||||||
|
new QName[]{new QName(MS_VML_URN, "shape")},
|
||||||
|
new QName[]{QNAME_SIGNATURE_LINE});
|
||||||
|
if (line != null) {
|
||||||
|
setSignatureShape(line);
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(XWPFParagraph paragraph) {
|
||||||
|
XWPFRun r = paragraph.createRun();
|
||||||
|
CTPicture pict = r.getCTR().addNewPict();
|
||||||
|
add(pict, (image, type) -> paragraph.getDocument().addPictureData(image, mapType(type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setRelationId(CTImageData imageData, String relId) {
|
||||||
|
imageData.setId2(relId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int mapType(PictureType type) throws InvalidFormatException {
|
||||||
|
switch (type) {
|
||||||
|
case BMP:
|
||||||
|
return Document.PICTURE_TYPE_BMP;
|
||||||
|
case DIB:
|
||||||
|
return Document.PICTURE_TYPE_DIB;
|
||||||
|
case EMF:
|
||||||
|
return Document.PICTURE_TYPE_EMF;
|
||||||
|
case EPS:
|
||||||
|
return Document.PICTURE_TYPE_EPS;
|
||||||
|
case GIF:
|
||||||
|
return Document.PICTURE_TYPE_GIF;
|
||||||
|
case JPEG:
|
||||||
|
return Document.PICTURE_TYPE_JPEG;
|
||||||
|
case PICT:
|
||||||
|
return Document.PICTURE_TYPE_PICT;
|
||||||
|
case PNG:
|
||||||
|
return Document.PICTURE_TYPE_PNG;
|
||||||
|
case TIFF:
|
||||||
|
return Document.PICTURE_TYPE_TIFF;
|
||||||
|
case WMF:
|
||||||
|
return Document.PICTURE_TYPE_WMF;
|
||||||
|
case WPG:
|
||||||
|
return Document.PICTURE_TYPE_WPG;
|
||||||
|
default:
|
||||||
|
throw new InvalidFormatException("Unsupported picture format "+type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,4 +43,8 @@
|
||||||
<xb:package>com.microsoft.schemas.compatibility</xb:package>
|
<xb:package>com.microsoft.schemas.compatibility</xb:package>
|
||||||
</xb:namespace>
|
</xb:namespace>
|
||||||
|
|
||||||
|
<xb:namespace uri="urn:schemas-poi-apache-org:vmldrawing">
|
||||||
|
<xb:package>org.apache.poi.schemas.vmldrawing</xb:package>
|
||||||
|
</xb:namespace>
|
||||||
|
|
||||||
</xb:config>
|
</xb:config>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xsd:schema
|
||||||
|
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns="urn:schemas-poi-apache-org:vmldrawing"
|
||||||
|
targetNamespace="urn:schemas-poi-apache-org:vmldrawing"
|
||||||
|
>
|
||||||
|
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
|
||||||
|
<xsd:import namespace="urn:schemas-microsoft-com:office:office" schemaLocation="vml-officeDrawing.xsd"/>
|
||||||
|
<xsd:import namespace="urn:schemas-microsoft-com:office:excel" schemaLocation="vml-spreadsheetDrawing.xsd"/>
|
||||||
|
<xsd:element name="xml" type="CT_XML"/>
|
||||||
|
<xsd:complexType name="CT_XML">
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:any namespace="urn:schemas-microsoft-com:office:office"/>
|
||||||
|
<xsd:any namespace="urn:schemas-microsoft-com:vml"/>
|
||||||
|
<xsd:any namespace="urn:schemas-microsoft-com:office:excel"/>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:schema>
|
|
@ -62,11 +62,14 @@ import java.security.cert.X509Certificate;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.security.spec.RSAKeyGenParameterSpec;
|
import java.security.spec.RSAKeyGenParameterSpec;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.xml.crypto.MarshalException;
|
import javax.xml.crypto.MarshalException;
|
||||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||||
|
@ -76,6 +79,7 @@ import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||||
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
|
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
|
||||||
import org.apache.poi.EncryptedDocumentException;
|
import org.apache.poi.EncryptedDocumentException;
|
||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
|
import org.apache.poi.ooxml.POIXMLDocument;
|
||||||
import org.apache.poi.ooxml.util.DocumentHelper;
|
import org.apache.poi.ooxml.util.DocumentHelper;
|
||||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
|
@ -98,9 +102,16 @@ import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LocaleUtil;
|
import org.apache.poi.util.LocaleUtil;
|
||||||
import org.apache.poi.util.POILogFactory;
|
import org.apache.poi.util.POILogFactory;
|
||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
|
import org.apache.poi.util.TempFile;
|
||||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSignatureLine;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFSignatureLine;
|
||||||
import org.apache.xmlbeans.SystemProperties;
|
import org.apache.xmlbeans.SystemProperties;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
import org.apache.xmlbeans.XmlObject;
|
import org.apache.xmlbeans.XmlObject;
|
||||||
import org.bouncycastle.asn1.DEROctetString;
|
import org.bouncycastle.asn1.DEROctetString;
|
||||||
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
|
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
|
||||||
|
@ -855,6 +866,100 @@ public class TestSignatureInfo {
|
||||||
assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod());
|
assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface XmlDocumentPackageInit {
|
||||||
|
POIXMLDocument init(SignatureLine line, OPCPackage pkg) throws IOException, XmlException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignatureImage() throws Exception {
|
||||||
|
initKeyPair();
|
||||||
|
|
||||||
|
List<Supplier<SignatureLine>> lines = Arrays.asList(XSSFSignatureLine::new, XWPFSignatureLine::new);
|
||||||
|
for (Supplier<SignatureLine> sup : lines) {
|
||||||
|
SignatureLine line = sup.get();
|
||||||
|
line.setSuggestedSigner("Jack Sparrow");
|
||||||
|
line.setSuggestedSigner2("Captain");
|
||||||
|
line.setSuggestedSignerEmail("jack.bl@ck.perl");
|
||||||
|
line.setInvalidStamp("Bungling!");
|
||||||
|
line.setPlainSignature(testdata.readFile("jack-sign.emf"));
|
||||||
|
|
||||||
|
String[] ext = { "" };
|
||||||
|
BiFunction<SignatureLine,String[],POIXMLDocument> init =
|
||||||
|
(line instanceof XSSFSignatureLine)
|
||||||
|
? this::initSignatureImageXSSF
|
||||||
|
: this::initSignatureImageXWPF;
|
||||||
|
|
||||||
|
File signDoc;
|
||||||
|
try (POIXMLDocument xmlDoc = init.apply(line,ext)) {
|
||||||
|
signDoc = TempFile.createTempFile("visual-signature", ext[0]);
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(signDoc)) {
|
||||||
|
xmlDoc.write(fos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ_WRITE)) {
|
||||||
|
SignatureConfig sic = new SignatureConfig();
|
||||||
|
sic.setKey(keyPair.getPrivate());
|
||||||
|
sic.setSigningCertificateChain(Collections.singletonList(x509));
|
||||||
|
|
||||||
|
line.updateSignatureConfig(sic);
|
||||||
|
|
||||||
|
sic.setDigestAlgo(HashAlgorithm.sha1);
|
||||||
|
SignatureInfo si = new SignatureInfo();
|
||||||
|
si.setOpcPackage(pkg);
|
||||||
|
si.setSignatureConfig(sic);
|
||||||
|
// hash > sha1 doesn't work in excel viewer ...
|
||||||
|
si.confirmSignature();
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlDocumentPackageInit reinit =
|
||||||
|
(line instanceof XSSFSignatureLine)
|
||||||
|
? this::initSignatureImageXSSF
|
||||||
|
: this::initSignatureImageXWPF;
|
||||||
|
|
||||||
|
try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ)) {
|
||||||
|
SignatureLine line2 = sup.get();
|
||||||
|
try (POIXMLDocument doc = reinit.init(line2, pkg)) {
|
||||||
|
line2.parse();
|
||||||
|
assertEquals(line.getSuggestedSigner(), line2.getSuggestedSigner());
|
||||||
|
assertEquals(line.getSuggestedSigner2(), line2.getSuggestedSigner2());
|
||||||
|
assertEquals(line.getSuggestedSignerEmail(), line2.getSuggestedSignerEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.revert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private XWPFDocument initSignatureImageXWPF(SignatureLine line, String[] ext) {
|
||||||
|
XWPFDocument doc = new XWPFDocument();
|
||||||
|
((XWPFSignatureLine)line).add(doc.createParagraph());
|
||||||
|
ext[0] = ".docx";
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XWPFDocument initSignatureImageXWPF(SignatureLine line, OPCPackage pkg) throws IOException, XmlException {
|
||||||
|
XWPFDocument doc = new XWPFDocument(pkg);
|
||||||
|
((XWPFSignatureLine)line).parse(doc);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XSSFWorkbook initSignatureImageXSSF(SignatureLine line, String[] ext) {
|
||||||
|
XSSFWorkbook xls = new XSSFWorkbook();
|
||||||
|
XSSFSheet sheet = xls.createSheet();
|
||||||
|
XSSFClientAnchor anchor = new XSSFClientAnchor(0,0,0,0,3,3,8,13);
|
||||||
|
((XSSFSignatureLine)line).add(sheet, anchor);
|
||||||
|
ext[0] = ".xlsx";
|
||||||
|
return xls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XSSFWorkbook initSignatureImageXSSF(SignatureLine line, OPCPackage pkg) throws IOException, XmlException {
|
||||||
|
XSSFWorkbook xls = new XSSFWorkbook(pkg);
|
||||||
|
((XSSFSignatureLine)line).parse(xls.getSheetAt(0));
|
||||||
|
return xls;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private SignatureConfig prepareConfig(String pfxInput) throws Exception {
|
private SignatureConfig prepareConfig(String pfxInput) throws Exception {
|
||||||
initKeyPair(pfxInput);
|
initKeyPair(pfxInput);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.xssf.usermodel;
|
package org.apache.poi.xssf.usermodel;
|
||||||
|
|
||||||
|
import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
|
||||||
|
import static org.apache.poi.xssf.usermodel.XSSFVMLDrawing.QNAME_VMLDRAWING;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
@ -27,14 +29,10 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.poi.POIDataSamples;
|
|
||||||
import org.apache.xmlbeans.XmlException;
|
|
||||||
import org.apache.xmlbeans.XmlObject;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.microsoft.schemas.office.excel.CTClientData;
|
import com.microsoft.schemas.office.excel.CTClientData;
|
||||||
import com.microsoft.schemas.office.excel.STObjectType;
|
import com.microsoft.schemas.office.excel.STObjectType;
|
||||||
import com.microsoft.schemas.office.excel.STTrueFalseBlank;
|
import com.microsoft.schemas.office.excel.STTrueFalseBlank;
|
||||||
|
@ -46,6 +44,11 @@ import com.microsoft.schemas.vml.CTShape;
|
||||||
import com.microsoft.schemas.vml.CTShapetype;
|
import com.microsoft.schemas.vml.CTShapetype;
|
||||||
import com.microsoft.schemas.vml.STExt;
|
import com.microsoft.schemas.vml.STExt;
|
||||||
import com.microsoft.schemas.vml.STTrueFalse;
|
import com.microsoft.schemas.vml.STTrueFalse;
|
||||||
|
import org.apache.poi.POIDataSamples;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
import org.apache.xmlbeans.XmlObject;
|
||||||
|
import org.apache.xmlbeans.XmlOptions;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestXSSFVMLDrawing {
|
public class TestXSSFVMLDrawing {
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ public class TestXSSFVMLDrawing {
|
||||||
assertEquals(STExt.EDIT, layout.getExt());
|
assertEquals(STExt.EDIT, layout.getExt());
|
||||||
assertEquals(STExt.EDIT, layout.getIdmap().getExt());
|
assertEquals(STExt.EDIT, layout.getIdmap().getExt());
|
||||||
assertEquals("1", layout.getIdmap().getData());
|
assertEquals("1", layout.getIdmap().getData());
|
||||||
|
|
||||||
assertTrue(items.get(1) instanceof CTShapetype);
|
assertTrue(items.get(1) instanceof CTShapetype);
|
||||||
CTShapetype type = (CTShapetype)items.get(1);
|
CTShapetype type = (CTShapetype)items.get(1);
|
||||||
assertEquals("21600,21600", type.getCoordsize());
|
assertEquals("21600,21600", type.getCoordsize());
|
||||||
|
@ -70,6 +73,7 @@ public class TestXSSFVMLDrawing {
|
||||||
assertEquals(STConnectType.RECT, type.getPathArray(0).getConnecttype());
|
assertEquals(STConnectType.RECT, type.getPathArray(0).getConnecttype());
|
||||||
|
|
||||||
CTShape shape = vml.newCommentShape();
|
CTShape shape = vml.newCommentShape();
|
||||||
|
items = vml.getItems();
|
||||||
assertEquals(3, items.size());
|
assertEquals(3, items.size());
|
||||||
assertSame(items.get(2), shape);
|
assertSame(items.get(2), shape);
|
||||||
assertEquals("#_x0000_t202", shape.getType());
|
assertEquals("#_x0000_t202", shape.getType());
|
||||||
|
@ -110,7 +114,7 @@ public class TestXSSFVMLDrawing {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFindCommentShape() throws IOException, XmlException {
|
public void testFindCommentShape() throws IOException, XmlException {
|
||||||
|
|
||||||
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
||||||
try (InputStream stream = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("vmlDrawing1.vml")) {
|
try (InputStream stream = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("vmlDrawing1.vml")) {
|
||||||
vml.read(stream);
|
vml.read(stream);
|
||||||
|
@ -158,17 +162,21 @@ public class TestXSSFVMLDrawing {
|
||||||
assertNull(vml.findCommentShape(0, 0));
|
assertNull(vml.findCommentShape(0, 0));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEvilUnclosedBRFixing() throws IOException, XmlException {
|
public void testEvilUnclosedBRFixing() throws IOException, XmlException {
|
||||||
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
||||||
try (InputStream stream = POIDataSamples.getOpenXML4JInstance().openResourceAsStream("bug-60626.vml")) {
|
try (InputStream stream = POIDataSamples.getOpenXML4JInstance().openResourceAsStream("bug-60626.vml")) {
|
||||||
vml.read(stream);
|
vml.read(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||||
|
xopt.setSaveImplicitNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||||
|
|
||||||
Pattern p = Pattern.compile("<br/>");
|
Pattern p = Pattern.compile("<br/>");
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (XmlObject xo : vml.getItems()) {
|
for (XmlObject xo : vml.getItems()) {
|
||||||
String[] split = p.split(xo.toString());
|
String[] split = p.split(xo.xmlText(xopt));
|
||||||
count += split.length-1;
|
count += split.length-1;
|
||||||
}
|
}
|
||||||
assertEquals(16, count);
|
assertEquals(16, count);
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue