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}">
|
||||
<zipfileset src="${ooxml.xsds.izip.1}"/>
|
||||
<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"/>
|
||||
<fileset dir="${ooxml.security.xsd.dir}" includes="signatureInfo.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 java.xml;
|
||||
|
||||
|
||||
exports com.microsoft.schemas.compatibility;
|
||||
exports com.microsoft.schemas.office.excel;
|
||||
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.vml;
|
||||
exports org.apache.poi.schemas.ooxml.system.ooxml;
|
||||
exports org.apache.poi.schemas.vmldrawing;
|
||||
exports org.etsi.uri.x01903.v13;
|
||||
exports org.openxmlformats.schemas.drawingml.x2006.chart;
|
||||
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.relationships;
|
||||
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
|
||||
* @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();
|
||||
|
||||
try {
|
||||
|
|
|
@ -17,14 +17,35 @@
|
|||
|
||||
package org.apache.poi.ooxml.util;
|
||||
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
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 {
|
||||
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() {}
|
||||
|
||||
|
@ -41,9 +62,165 @@ public final class XPathHelper {
|
|||
try {
|
||||
xpf.setFeature(feature, enabled);
|
||||
} catch (Exception e) {
|
||||
logger.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
|
||||
LOG.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
|
||||
} 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 org.apache.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.hpsf.ClassID;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
|
||||
|
@ -89,10 +90,10 @@ public class SignatureConfig {
|
|||
);
|
||||
|
||||
|
||||
private ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
|
||||
private ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
|
||||
private ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
|
||||
private ThreadLocal<Provider> provider = new ThreadLocal<>();
|
||||
private final ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
|
||||
private final ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
|
||||
private final ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
|
||||
private final ThreadLocal<Provider> provider = new ThreadLocal<>();
|
||||
|
||||
private List<SignatureFacet> signatureFacets = new ArrayList<>();
|
||||
private HashAlgorithm digestAlgo = HashAlgorithm.sha256;
|
||||
|
@ -165,6 +166,26 @@ public class SignatureConfig {
|
|||
*/
|
||||
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.
|
||||
* This also includes the canonicalization. Currently this leads to problems
|
||||
|
@ -386,6 +407,38 @@ public class SignatureConfig {
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
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.PackagingURIHelper;
|
||||
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.SignatureInfo;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
|
||||
|
@ -256,10 +258,20 @@ public class OOXMLSignatureFacet implements SignatureFacet {
|
|||
|
||||
SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
|
||||
CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
|
||||
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
|
||||
if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) {
|
||||
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
|
||||
}
|
||||
|
||||
if (signatureConfig.getSignatureDescription() != null) {
|
||||
ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
|
||||
String desc = 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);
|
||||
|
@ -282,6 +294,27 @@ public class OOXMLSignatureFacet implements SignatureFacet {
|
|||
|
||||
Reference reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
|
||||
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) {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.apache.poi.xslf.model;
|
||||
|
||||
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
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.PML_NS;
|
||||
|
||||
|
@ -37,7 +38,7 @@ public abstract class TextBodyPropertyFetcher<T> extends PropertyFetcher<T> {
|
|||
public boolean fetch(XSLFShape shape) {
|
||||
CTTextBodyProperties props = null;
|
||||
try {
|
||||
props = shape.selectProperty(
|
||||
props = selectProperty(shape.getXmlObject(),
|
||||
CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
|
||||
return (props != null) && fetch(props);
|
||||
} catch (XmlException e) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import javax.xml.stream.XMLStreamReader;
|
|||
import org.apache.poi.hpsf.ClassID;
|
||||
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
|
||||
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.opc.OPCPackage;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
|
@ -76,7 +77,7 @@ public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape<XSL
|
|||
// select oleObj potentially under AlternateContent
|
||||
// usually the mc:Choice element will be selected first
|
||||
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) {
|
||||
// ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
|
||||
throw new IllegalStateException(e);
|
||||
|
@ -146,8 +147,8 @@ public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape<XSL
|
|||
|
||||
protected CTBlipFillProperties getBlipFill() {
|
||||
try {
|
||||
CTPicture pic = selectProperty
|
||||
(CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
|
||||
CTPicture pic = XPathHelper.selectProperty
|
||||
(getXmlObject(), CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
|
||||
return (pic != null) ? pic.getBlipFill() : null;
|
||||
} catch (XmlException e) {
|
||||
return null;
|
||||
|
|
|
@ -35,6 +35,7 @@ import javax.imageio.ImageIO;
|
|||
import javax.xml.namespace.QName;
|
||||
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.PackageRelationship;
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
|
@ -175,7 +176,7 @@ public class XSLFPictureShape extends XSLFSimpleShape
|
|||
}
|
||||
|
||||
try {
|
||||
return selectProperty(CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
|
||||
return XPathHelper.selectProperty(getXmlObject(), CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
|
||||
} catch (XmlException xe) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
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 java.util.function.Consumer;
|
||||
|
@ -220,7 +221,7 @@ public class XSLFPlaceholderDetails implements PlaceholderDetails {
|
|||
|
||||
private CTApplicationNonVisualDrawingProps getNvProps() {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -21,13 +21,11 @@ package org.apache.poi.xslf.usermodel;
|
|||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
|
||||
import com.microsoft.schemas.compatibility.AlternateContentDocument;
|
||||
import com.microsoft.schemas.compatibility.AlternateContentDocument.AlternateContent;
|
||||
import org.apache.poi.ooxml.util.XPathHelper;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
import org.apache.poi.sl.draw.DrawFactory;
|
||||
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.XmlException;
|
||||
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.CTGradientFillProperties;
|
||||
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 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 = {
|
||||
new QName(PML_NS, "nvSpPr"),
|
||||
|
@ -93,12 +86,6 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
|||
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 XSLFSheet _sheet;
|
||||
private XSLFShapeContainer _parent;
|
||||
|
@ -239,7 +226,7 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
|||
protected CTNonVisualDrawingProps getCNvPr() {
|
||||
try {
|
||||
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;
|
||||
} catch (XmlException e) {
|
||||
|
@ -322,160 +309,6 @@ public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
|
|||
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>
|
||||
*
|
||||
|
|
|
@ -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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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.STObjectType;
|
||||
import com.microsoft.schemas.office.office.CTIdMap;
|
||||
import com.microsoft.schemas.office.office.CTShapeLayout;
|
||||
import com.microsoft.schemas.office.office.STConnectType;
|
||||
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.CTShadow;
|
||||
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.STStrokeJoinStyle;
|
||||
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.
|
||||
|
@ -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
|
||||
* need a file format for drawings are strongly encouraged to use preferentially DrawingML
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Warning - Excel is known to put invalid XML into these files!
|
||||
* For example, >br< without being closed or escaped crops up.
|
||||
* </p>
|
||||
*
|
||||
* 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 {
|
||||
private static final QName QNAME_SHAPE_LAYOUT = new QName("urn:schemas-microsoft-com:office:office", "shapelayout");
|
||||
private static final QName QNAME_SHAPE_TYPE = new QName("urn:schemas-microsoft-com:vml", "shapetype");
|
||||
private static final QName QNAME_SHAPE = new QName("urn:schemas-microsoft-com:vml", "shape");
|
||||
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
|
||||
// this ID value seems to have significance to Excel >= 2010;
|
||||
// see https://issues.apache.org/bugzilla/show_bug.cgi?id=55409
|
||||
private static final String COMMENT_SHAPE_TYPE_ID = "_x0000_t202";
|
||||
|
||||
/**
|
||||
* 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"
|
||||
*/
|
||||
private static final Pattern ptrn_shapeId = Pattern.compile("_x0000_s(\\d+)");
|
||||
|
||||
private List<QName> _qnames = new ArrayList<>();
|
||||
private List<XmlObject> _items = new ArrayList<>();
|
||||
private XmlDocument root;
|
||||
private String _shapeTypeId;
|
||||
private int _shapeId = 1024;
|
||||
|
||||
|
@ -112,7 +115,7 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
*
|
||||
* @param part the package part holding the drawing data,
|
||||
* the content type must be <code>application/vnd.openxmlformats-officedocument.drawing+xml</code>
|
||||
*
|
||||
*
|
||||
* @since POI 3.14-Beta1
|
||||
*/
|
||||
protected XSSFVMLDrawing(PackagePart part) throws IOException, XmlException {
|
||||
|
@ -120,6 +123,11 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
read(getPackagePart().getInputStream());
|
||||
}
|
||||
|
||||
public XmlDocument getDocument() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
protected void read(InputStream is) throws IOException, XmlException {
|
||||
Document doc;
|
||||
try {
|
||||
|
@ -133,92 +141,84 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
} catch (SAXException e) {
|
||||
throw new XmlException(e.getMessage(), e);
|
||||
}
|
||||
XmlObject root = XmlObject.Factory.parse(doc, DEFAULT_XML_OPTIONS);
|
||||
|
||||
_qnames = new ArrayList<>();
|
||||
_items = new ArrayList<>();
|
||||
for(XmlObject obj : root.selectPath("$this/xml/*")) {
|
||||
Node nd = obj.getDomNode();
|
||||
QName qname = new QName(nd.getNamespaceURI(), nd.getLocalName());
|
||||
if (qname.equals(QNAME_SHAPE_LAYOUT)) {
|
||||
_items.add(CTShapeLayout.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS));
|
||||
} else if (qname.equals(QNAME_SHAPE_TYPE)) {
|
||||
CTShapetype st = CTShapetype.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS);
|
||||
_items.add(st);
|
||||
_shapeTypeId = st.getId();
|
||||
} else if (qname.equals(QNAME_SHAPE)) {
|
||||
CTShape shape = CTShape.Factory.parse(obj.xmlText(), DEFAULT_XML_OPTIONS);
|
||||
String id = shape.getId();
|
||||
if(id != null) {
|
||||
Matcher m = ptrn_shapeId.matcher(id);
|
||||
if(m.find()) {
|
||||
_shapeId = Math.max(_shapeId, Integer.parseInt(m.group(1)));
|
||||
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||
xopt.setLoadSubstituteNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||
|
||||
root = XmlDocument.Factory.parse(doc, xopt);
|
||||
XmlCursor cur = root.getXml().newCursor();
|
||||
|
||||
try {
|
||||
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||
XmlObject xo = cur.getObject();
|
||||
if (xo instanceof CTShapetype) {
|
||||
_shapeTypeId = ((CTShapetype)xo).getId();
|
||||
} else if (xo instanceof CTShape) {
|
||||
CTShape shape = (CTShape)xo;
|
||||
String id = shape.getId();
|
||||
if(id != null) {
|
||||
Matcher m = ptrn_shapeId.matcher(id);
|
||||
if(m.find()) {
|
||||
_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(){
|
||||
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 {
|
||||
XmlObject rootObject = XmlObject.Factory.newInstance();
|
||||
XmlCursor rootCursor = rootObject.newCursor();
|
||||
rootCursor.toNextToken();
|
||||
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);
|
||||
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||
xopt.setSaveImplicitNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||
root.save(out, xopt);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void commit() throws IOException {
|
||||
PackagePart part = getPackagePart();
|
||||
OutputStream out = part.getOutputStream();
|
||||
write(out);
|
||||
out.close();
|
||||
try (OutputStream out = part.getOutputStream()) {
|
||||
write(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new Speadsheet VML drawing
|
||||
*/
|
||||
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);
|
||||
CTIdMap idmap = layout.addNewIdmap();
|
||||
idmap.setExt(STExt.EDIT);
|
||||
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;
|
||||
shapetype.setId(_shapeTypeId);
|
||||
shapetype.setCoordsize("21600,21600");
|
||||
|
@ -228,12 +228,17 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
CTPath path = shapetype.addNewPath();
|
||||
path.setGradientshapeok(STTrueFalse.T);
|
||||
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(){
|
||||
CTShape shape = CTShape.Factory.newInstance();
|
||||
CTGroup grp = CTGroup.Factory.newInstance();
|
||||
|
||||
CTShape shape = grp.addNewShape();
|
||||
shape.setId("_x0000_s" + (++_shapeId));
|
||||
shape.setType("#" + _shapeTypeId);
|
||||
shape.setStyle("position:absolute; visibility:hidden");
|
||||
|
@ -254,8 +259,16 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
cldata.addNewAutoFill().setStringValue("False");
|
||||
cldata.addNewRow().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;
|
||||
}
|
||||
|
||||
|
@ -265,26 +278,45 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
|
|||
* @return the comment shape or <code>null</code>
|
||||
*/
|
||||
public CTShape findCommentShape(int row, int col){
|
||||
for(XmlObject itm : _items){
|
||||
if(itm instanceof CTShape){
|
||||
CTShape sh = (CTShape)itm;
|
||||
if(sh.sizeOfClientDataArray() > 0){
|
||||
CTClientData cldata = sh.getClientDataArray(0);
|
||||
if(cldata.getObjectType() == STObjectType.NOTE){
|
||||
int crow = cldata.getRowArray(0).intValue();
|
||||
int ccol = cldata.getColumnArray(0).intValue();
|
||||
if(crow == row && ccol == col) {
|
||||
return sh;
|
||||
}
|
||||
}
|
||||
}
|
||||
XmlCursor cur = root.getXml().newCursor();
|
||||
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
|
||||
XmlObject itm = cur.getObject();
|
||||
if (matchCommentShape(itm, row, col)) {
|
||||
return (CTShape)itm;
|
||||
}
|
||||
}
|
||||
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){
|
||||
CTShape shape = findCommentShape(row, col);
|
||||
return shape != null && _items.remove(shape);
|
||||
XmlCursor cur = root.getXml().newCursor();
|
||||
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:namespace>
|
||||
|
||||
<xb:namespace uri="urn:schemas-poi-apache-org:vmldrawing">
|
||||
<xb:package>org.apache.poi.schemas.vmldrawing</xb:package>
|
||||
</xb:namespace>
|
||||
|
||||
</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.spec.RSAKeyGenParameterSpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.xml.crypto.MarshalException;
|
||||
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.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.POIDataSamples;
|
||||
import org.apache.poi.ooxml.POIXMLDocument;
|
||||
import org.apache.poi.ooxml.util.DocumentHelper;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
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.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.poi.util.TempFile;
|
||||
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.xwpf.usermodel.XWPFDocument;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFSignatureLine;
|
||||
import org.apache.xmlbeans.SystemProperties;
|
||||
import org.apache.xmlbeans.XmlException;
|
||||
import org.apache.xmlbeans.XmlObject;
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
|
||||
|
@ -855,6 +866,100 @@ public class TestSignatureInfo {
|
|||
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 {
|
||||
initKeyPair(pfxInput);
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
==================================================================== */
|
||||
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.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
@ -27,14 +29,10 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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.STObjectType;
|
||||
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.STExt;
|
||||
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 {
|
||||
|
||||
|
@ -59,7 +62,7 @@ public class TestXSSFVMLDrawing {
|
|||
assertEquals(STExt.EDIT, layout.getExt());
|
||||
assertEquals(STExt.EDIT, layout.getIdmap().getExt());
|
||||
assertEquals("1", layout.getIdmap().getData());
|
||||
|
||||
|
||||
assertTrue(items.get(1) instanceof CTShapetype);
|
||||
CTShapetype type = (CTShapetype)items.get(1);
|
||||
assertEquals("21600,21600", type.getCoordsize());
|
||||
|
@ -70,6 +73,7 @@ public class TestXSSFVMLDrawing {
|
|||
assertEquals(STConnectType.RECT, type.getPathArray(0).getConnecttype());
|
||||
|
||||
CTShape shape = vml.newCommentShape();
|
||||
items = vml.getItems();
|
||||
assertEquals(3, items.size());
|
||||
assertSame(items.get(2), shape);
|
||||
assertEquals("#_x0000_t202", shape.getType());
|
||||
|
@ -110,7 +114,7 @@ public class TestXSSFVMLDrawing {
|
|||
|
||||
@Test
|
||||
public void testFindCommentShape() throws IOException, XmlException {
|
||||
|
||||
|
||||
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
||||
try (InputStream stream = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("vmlDrawing1.vml")) {
|
||||
vml.read(stream);
|
||||
|
@ -158,17 +162,21 @@ public class TestXSSFVMLDrawing {
|
|||
assertNull(vml.findCommentShape(0, 0));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEvilUnclosedBRFixing() throws IOException, XmlException {
|
||||
XSSFVMLDrawing vml = new XSSFVMLDrawing();
|
||||
try (InputStream stream = POIDataSamples.getOpenXML4JInstance().openResourceAsStream("bug-60626.vml")) {
|
||||
vml.read(stream);
|
||||
}
|
||||
|
||||
XmlOptions xopt = new XmlOptions(DEFAULT_XML_OPTIONS);
|
||||
xopt.setSaveImplicitNamespaces(Collections.singletonMap("", QNAME_VMLDRAWING.getNamespaceURI()));
|
||||
|
||||
Pattern p = Pattern.compile("<br/>");
|
||||
int count = 0;
|
||||
for (XmlObject xo : vml.getItems()) {
|
||||
String[] split = p.split(xo.toString());
|
||||
String[] split = p.split(xo.xmlText(xopt));
|
||||
count += split.length-1;
|
||||
}
|
||||
assertEquals(16, count);
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue