<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"/>
--- /dev/null
+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;
+ }
+}
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;
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;
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;
}
\ No newline at end of file
* @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 {
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() {}
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();
+ }
+ }
+ }
+
}
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;
);
- 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;
*/
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
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
*/
--- /dev/null
+/* ====================================================================
+ 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");
+ }
+ }
+
+}
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;
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;
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);
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) {
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;
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);
}
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;
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) {
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;
// 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);
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;
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;
}
try {
- return selectProperty(CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
+ return XPathHelper.selectProperty(getXmlObject(), CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
} catch (XmlException xe) {
return null;
}
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;
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;
}
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;
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;
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"),
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;
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) {
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>
*
--- /dev/null
+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);
+ }
+ }
+}
\ No newline at end of file
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;
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.
* considered a deprecated format included in Office Open XML for legacy reasons only and new applications that
* need a file format for drawings are strongly encouraged to use preferentially DrawingML
* </p>
- *
+ *
* <p>
* Warning - Excel is known to put invalid XML into these files!
* For example, >br< without being closed or escaped crops up.
* </p>
*
* See 6.4 VML - SpreadsheetML Drawing in Office Open XML Part 4 - Markup Language Reference.pdf
- *
- * @author Yegor Kozlov
*/
public final class XSSFVMLDrawing extends POIXMLDocumentPart {
- private static final QName QNAME_SHAPE_LAYOUT = new QName("urn:schemas-microsoft-com:office:office", "shapelayout");
- private static final QName QNAME_SHAPE_TYPE = new QName("urn:schemas-microsoft-com:vml", "shapetype");
- private static final QName QNAME_SHAPE = new QName("urn:schemas-microsoft-com:vml", "shape");
- private static final String COMMENT_SHAPE_TYPE_ID = "_x0000_t202"; // this ID value seems to have significance to Excel >= 2010; see https://issues.apache.org/bugzilla/show_bug.cgi?id=55409
+ // this ID value seems to have significance to Excel >= 2010;
+ // see https://issues.apache.org/bugzilla/show_bug.cgi?id=55409
+ private static final String COMMENT_SHAPE_TYPE_ID = "_x0000_t202";
+
+ /**
+ * to actually process the namespace-less vmldrawing, we've introduced a proxy namespace.
+ * this namespace is active in-memory, but will be removed on saving to the file
+ */
+ public static final QName QNAME_VMLDRAWING = new QName("urn:schemas-poi-apache-org:vmldrawing", "xml");
/**
* regexp to parse shape ids, in VML they have weird form of id="_x0000_s1026"
*/
private static final Pattern ptrn_shapeId = Pattern.compile("_x0000_s(\\d+)");
- private List<QName> _qnames = new ArrayList<>();
- private List<XmlObject> _items = new ArrayList<>();
+ private XmlDocument root;
private String _shapeTypeId;
private int _shapeId = 1024;
*
* @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 {
read(getPackagePart().getInputStream());
}
+ public XmlDocument getDocument() {
+ return root;
+ }
+
+
protected void read(InputStream is) throws IOException, XmlException {
Document doc;
try {
} 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");
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");
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;
}
* @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;
}
}
\ No newline at end of file
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+ }
+}
<xb:package>com.microsoft.schemas.compatibility</xb:package>\r
</xb:namespace>\r
\r
+ <xb:namespace uri="urn:schemas-poi-apache-org:vmldrawing">\r
+ <xb:package>org.apache.poi.schemas.vmldrawing</xb:package>\r
+ </xb:namespace>\r
+\r
</xb:config>
\ No newline at end of file
--- /dev/null
+<?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>
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;
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;
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;
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);
==================================================================== */
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;
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;
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 {
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());
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());
@Test
public void testFindCommentShape() throws IOException, XmlException {
-
+
XSSFVMLDrawing vml = new XSSFVMLDrawing();
try (InputStream stream = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("vmlDrawing1.vml")) {
vml.read(stream);
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);