#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:
Andreas Beeker 2020-10-10 23:33:26 +00:00
parent 5d2d6fee1a
commit 2a292cb42d
25 changed files with 1323 additions and 301 deletions

View File

@ -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"/>

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
} }

View File

@ -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 {

View File

@ -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();
}
}
}
} }

View File

@ -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
*/ */

View File

@ -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");
}
}
}

View File

@ -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();
if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) {
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); 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) {

View File

@ -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);
} }

View File

@ -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) {

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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>
* *

View File

@ -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);
}
}
}

View File

@ -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.
@ -78,22 +79,24 @@ import com.microsoft.schemas.vml.STTrueFalse;
* </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;
@ -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,21 +141,20 @@ 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);
@ -155,70 +162,63 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart {
_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;
} }
} }

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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);

View File

@ -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 {
@ -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());
@ -165,10 +169,14 @@ public class TestXSSFVMLDrawing {
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.