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