Browse Source

#64773 - Visual signatures for .xlsx/.docx

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1882394 13f79535-47bb-0310-9956-ffa450edef68
tags/before_ooxml_3rd_edition
Andreas Beeker 3 years ago
parent
commit
2a292cb42d
25 changed files with 1323 additions and 301 deletions
  1. 1
    1
      build.xml
  2. 51
    0
      src/java/org/apache/poi/common/usermodel/PictureType.java
  3. BIN
      src/multimodule/ooxml-lite/java9/module-info.class
  4. 2
    0
      src/multimodule/ooxml-lite/java9/module-info.java
  5. BIN
      src/multimodule/ooxml-schemas/java9/module-info.class
  6. 2
    0
      src/multimodule/ooxml-schemas/java9/module-info.java
  7. 2
    1
      src/ooxml/java/org/apache/poi/ooxml/POIXMLDocumentPart.java
  8. 182
    5
      src/ooxml/java/org/apache/poi/ooxml/util/XPathHelper.java
  9. 57
    4
      src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
  10. 489
    0
      src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java
  11. 36
    3
      src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
  12. 3
    1
      src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java
  13. 2
    1
      src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java
  14. 4
    3
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java
  15. 2
    1
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java
  16. 2
    1
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
  17. 2
    169
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
  18. 117
    0
      src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSignatureLine.java
  19. 134
    102
      src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java
  20. 91
    0
      src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSignatureLine.java
  21. 4
    0
      src/ooxml/resources/org/apache/poi/schemas/ooxmlSchemas.xsdconfig
  22. 18
    0
      src/ooxml/resources/org/apache/poi/schemas/vmlDrawing.xsd
  23. 105
    0
      src/ooxml/testcases/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java
  24. 17
    9
      src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFVMLDrawing.java
  25. BIN
      test-data/xmldsign/jack-sign.emf

+ 1
- 1
build.xml View File

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

+ 51
- 0
src/java/org/apache/poi/common/usermodel/PictureType.java 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;
}
}

BIN
src/multimodule/ooxml-lite/java9/module-info.class View File


+ 2
- 0
src/multimodule/ooxml-lite/java9/module-info.java View File

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

BIN
src/multimodule/ooxml-schemas/java9/module-info.class View File


+ 2
- 0
src/multimodule/ooxml-schemas/java9/module-info.java 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.relationships;
exports org.w3.x2000.x09.xmldsig;

exports org.apache.poi.schemas.vmldrawing;
}

+ 2
- 1
src/ooxml/java/org/apache/poi/ooxml/POIXMLDocumentPart.java View File

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

+ 182
- 5
src/ooxml/java/org/apache/poi/ooxml/util/XPathHelper.java View File

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

}

+ 57
- 4
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java View File

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

+ 489
- 0
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureLine.java 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");
}
}

}

+ 36
- 3
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java View File

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

String desc = signatureConfig.getSignatureDescription();
if (desc != null) {
ctSigV1.setSignatureComments(desc);
}

if (signatureConfig.getSignatureDescription() != null) {
ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
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) {

+ 3
- 1
src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java View File

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


+ 2
- 1
src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java View File

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

+ 4
- 3
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java View File

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

+ 2
- 1
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java View File

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

+ 2
- 1
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java View File

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

+ 2
- 169
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java View File

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

+ 117
- 0
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSignatureLine.java 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);
}
}
}

+ 134
- 102
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java View File

@@ -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, &gt;br&lt; 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<>();

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());
XmlCursor cur = root.getXml().newCursor();
try {
for (boolean found = cur.toFirstChild(); found; found = cur.toNextSibling()) {
items.add(cur.getObject());
}
xc.toStartDoc();
xc.copyXmlContents(rootCursor);
rootCursor.toNextToken();
xc.dispose();
} finally {
cur.dispose();
}
rootCursor.dispose();

rootObject.save(out, DEFAULT_XML_OPTIONS);
return items;
}

protected void write(OutputStream out) throws IOException {
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;
}
}

+ 91
- 0
src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSignatureLine.java 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);
}
}
}

+ 4
- 0
src/ooxml/resources/org/apache/poi/schemas/ooxmlSchemas.xsdconfig View File

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

+ 18
- 0
src/ooxml/resources/org/apache/poi/schemas/vmlDrawing.xsd 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>

+ 105
- 0
src/ooxml/testcases/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java View File

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


+ 17
- 9
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFVMLDrawing.java View File

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

BIN
test-data/xmldsign/jack-sign.emf View File


Loading…
Cancel
Save