View | Details | Raw Unified | Return to bug 64773
Collapse All | Expand All

(-)src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java (-3 / +37 lines)
Lines 31-36 Link Here
31
import java.net.URISyntaxException;
31
import java.net.URISyntaxException;
32
import java.util.ArrayList;
32
import java.util.ArrayList;
33
import java.util.Arrays;
33
import java.util.Arrays;
34
import java.util.Base64;
34
import java.util.Collections;
35
import java.util.Collections;
35
import java.util.Comparator;
36
import java.util.Comparator;
36
import java.util.HashSet;
37
import java.util.HashSet;
Lines 62-67 Link Here
62
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
63
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
63
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
64
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
64
import org.apache.poi.openxml4j.opc.TargetMode;
65
import org.apache.poi.openxml4j.opc.TargetMode;
66
import org.apache.poi.poifs.crypt.HashAlgorithm;
65
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
67
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
66
import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
68
import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
67
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
69
import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
Lines 256-265 Link Here
256
258
257
        SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
259
        SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
258
        CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
260
        CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
259
        ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
261
        if (signatureConfig.getDigestAlgo() != HashAlgorithm.sha1) {
262
            ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
263
        }
260
264
261
        if (signatureConfig.getSignatureDescription() != null) {
265
        String desc = signatureConfig.getSignatureDescription();
262
            ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
266
        if (desc != null) {
267
            ctSigV1.setSignatureComments(desc);
268
        }
269
270
        byte[] image = signatureConfig.getSignatureImage();
271
        if (image != null) {
272
            // libre office is not showing anything without the setup id ...
273
            ctSigV1.setSetupID(signatureConfig.getSignatureImageSetupId().toString());
274
            ctSigV1.setSignatureImage(image);
275
            ctSigV1.setSignatureType(2);
263
        }
276
        }
264
277
265
        Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
278
        Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
Lines 282-287 Link Here
282
295
283
        Reference reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
296
        Reference reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
284
        references.add(reference);
297
        references.add(reference);
298
299
        Base64.Encoder enc = Base64.getEncoder();
300
        byte[] imageValid = signatureConfig.getSignatureImageValid();
301
        if (imageValid != null) {
302
            objectId = "idValidSigLnImg";
303
            DOMStructure tn = new DOMStructure(document.createTextNode(enc.encodeToString(imageValid)));
304
            objects.add(sigFac.newXMLObject(Collections.singletonList(tn), objectId, null, null));
305
306
            reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
307
            references.add(reference);
308
        }
309
310
        byte[] imageInvalid = signatureConfig.getSignatureImageInvalid();
311
        if (imageInvalid != null) {
312
            objectId = "idInvalidSigLnImg";
313
            DOMStructure tn = new DOMStructure(document.createTextNode(enc.encodeToString(imageInvalid)));
314
            objects.add(sigFac.newXMLObject(Collections.singletonList(tn), objectId, null, null));
315
316
            reference = newReference(signatureInfo, "#" + objectId, null, XML_DIGSIG_NS+"Object", null, null);
317
            references.add(reference);
318
        }
285
    }
319
    }
286
320
287
    protected static String getRelationshipReferenceURI(String zipEntryName) {
321
    protected static String getRelationshipReferenceURI(String zipEntryName) {
(-)src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java (-1 / +3 lines)
Lines 19-24 Link Here
19
19
20
package org.apache.poi.xslf.model;
20
package org.apache.poi.xslf.model;
21
21
22
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
23
22
import java.util.function.Consumer;
24
import java.util.function.Consumer;
23
25
24
import javax.xml.namespace.QName;
26
import javax.xml.namespace.QName;
Lines 113-119 Link Here
113
115
114
    static CTTextParagraphProperties select(XSLFShape shape, int level) throws XmlException {
116
    static CTTextParagraphProperties select(XSLFShape shape, int level) throws XmlException {
115
        QName[] lvlProp = { new QName(DML_NS, "lvl" + (level + 1) + "pPr") };
117
        QName[] lvlProp = { new QName(DML_NS, "lvl" + (level + 1) + "pPr") };
116
        return shape.selectProperty(
118
        return selectProperty(shape.getXmlObject(),
117
            CTTextParagraphProperties.class, ParagraphPropertyFetcher::parse, TX_BODY, LST_STYLE, lvlProp);
119
            CTTextParagraphProperties.class, ParagraphPropertyFetcher::parse, TX_BODY, LST_STYLE, lvlProp);
118
    }
120
    }
119
121
(-)src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java (-4 / +48 lines)
Lines 45-50 Link Here
45
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
45
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
46
46
47
import org.apache.poi.EncryptedDocumentException;
47
import org.apache.poi.EncryptedDocumentException;
48
import org.apache.poi.hpsf.ClassID;
48
import org.apache.poi.openxml4j.opc.OPCPackage;
49
import org.apache.poi.openxml4j.opc.OPCPackage;
49
import org.apache.poi.poifs.crypt.HashAlgorithm;
50
import org.apache.poi.poifs.crypt.HashAlgorithm;
50
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
51
import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
Lines 89-98 Link Here
89
    );
90
    );
90
91
91
92
92
    private ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
93
    private final ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<>();
93
    private ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
94
    private final ThreadLocal<XMLSignatureFactory> signatureFactory = new ThreadLocal<>();
94
    private ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
95
    private final ThreadLocal<KeyInfoFactory> keyInfoFactory = new ThreadLocal<>();
95
    private ThreadLocal<Provider> provider = new ThreadLocal<>();
96
    private final ThreadLocal<Provider> provider = new ThreadLocal<>();
96
97
97
    private List<SignatureFacet> signatureFacets = new ArrayList<>();
98
    private List<SignatureFacet> signatureFacets = new ArrayList<>();
98
    private HashAlgorithm digestAlgo = HashAlgorithm.sha256;
99
    private HashAlgorithm digestAlgo = HashAlgorithm.sha256;
Lines 165-170 Link Here
165
     */
166
     */
166
    private String signatureDescription = "Office OpenXML Document";
167
    private String signatureDescription = "Office OpenXML Document";
167
168
169
    /**
170
     * Only applies when working with visual signatures:
171
     * Specifies a GUID which can be cross-referenced with the GUID of the signature line stored in the document content.
172
     * I.e. the signatureline element id attribute in the document/sheet has to be references in the SetupId element.
173
     */
174
    private ClassID signatureImageSetupId;
175
176
    /**
177
     * Provides a signature image for visual signature lines
178
     */
179
    private byte[] signatureImage;
180
    /**
181
     * The image shown, when the signature is valid
182
     */
183
    private byte[] signatureImageValid;
184
    /**
185
     * The image shown, when the signature is invalid
186
     */
187
    private byte[] signatureImageInvalid;
188
168
    /**
189
    /**
169
     * The process of signing includes the marshalling of xml structures.
190
     * The process of signing includes the marshalling of xml structures.
170
     * This also includes the canonicalization. Currently this leads to problems
191
     * This also includes the canonicalization. Currently this leads to problems
Lines 386-391 Link Here
386
        this.signatureDescription = signatureDescription;
407
        this.signatureDescription = signatureDescription;
387
    }
408
    }
388
409
410
    public byte[] getSignatureImage() {
411
        return signatureImage;
412
    }
413
414
    public byte[] getSignatureImageValid() {
415
        return signatureImageValid;
416
    }
417
418
    public byte[] getSignatureImageInvalid() {
419
        return signatureImageInvalid;
420
    }
421
422
    public ClassID getSignatureImageSetupId() {
423
        return signatureImageSetupId;
424
    }
425
426
    public void setSignatureImage(ClassID signatureImageSetupId, byte[] signatureImage, byte[] signatureImageValid, byte[] signatureImageInvalid) {
427
        this.signatureImageSetupId = signatureImageSetupId;
428
        this.signatureImage = signatureImage;
429
        this.signatureImageValid = signatureImageValid;
430
        this.signatureImageInvalid = signatureImageInvalid;
431
    }
432
389
    /**
433
    /**
390
     * @return the default canonicalization method, defaults to INCLUSIVE
434
     * @return the default canonicalization method, defaults to INCLUSIVE
391
     */
435
     */
(-)src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureImage.java (+162 lines)
Line 0 Link Here
1
/* ====================================================================
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
4
   this work for additional information regarding copyright ownership.
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
   (the "License"); you may not use this file except in compliance with
7
   the License.  You may obtain a copy of the License at
8
9
       http://www.apache.org/licenses/LICENSE-2.0
10
11
   Unless required by applicable law or agreed to in writing, software
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
16
==================================================================== */
17
18
package org.apache.poi.poifs.crypt.dsig;
19
20
import java.awt.AlphaComposite;
21
import java.awt.Color;
22
import java.awt.Font;
23
import java.awt.GradientPaint;
24
import java.awt.Graphics2D;
25
import java.awt.RenderingHints;
26
import java.awt.Shape;
27
import java.awt.font.FontRenderContext;
28
import java.awt.font.LineBreakMeasurer;
29
import java.awt.font.TextAttribute;
30
import java.awt.font.TextLayout;
31
import java.awt.geom.AffineTransform;
32
import java.awt.geom.Dimension2D;
33
import java.awt.geom.Rectangle2D;
34
import java.awt.image.BufferedImage;
35
import java.io.ByteArrayOutputStream;
36
import java.io.IOException;
37
import java.text.AttributedCharacterIterator;
38
import java.text.AttributedString;
39
40
import javax.imageio.ImageIO;
41
42
import org.apache.poi.poifs.filesystem.FileMagic;
43
import org.apache.poi.sl.draw.DrawPictureShape;
44
import org.apache.poi.sl.draw.ImageRenderer;
45
import org.apache.poi.sl.usermodel.PictureData.PictureType;
46
import org.apache.poi.util.Beta;
47
48
/**
49
 * Makeshift helper class to generate signature images, feel free to provide your own
50
 */
51
@Beta
52
public class SignatureImage {
53
    /**
54
     * Generate the image for a signature line
55
     * @param caption three lines separated by "\n" - usually something like "First name Last name\nRole\nname of the key"
56
     * @param inputImage the plain signature - supported formats are PNG,GIF,JPEG,(SVG),EMF,WMF.
57
     *                   for SVG,EMF,WMF poi-scratchpad needs to be in the class-/modulepath
58
     *                   if {@code null}, the inputImage is not rendered
59
     * @param invalidText for invalid signature images, use the given text
60
     * @return the signature image in PNG format as byte array
61
     */
62
    public static byte[] generateImage(String caption, byte[] inputImage, String invalidText) throws IOException {
63
        BufferedImage bi = new BufferedImage(400, 150, BufferedImage.TYPE_INT_ARGB);
64
        Graphics2D gfx = bi.createGraphics();
65
        gfx.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
66
        gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
67
        gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
68
        gfx.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
69
70
        String markX = "X\n";
71
        String lineX = (new String(new char[500]).replace("\0", " ")) +"\n";
72
        String text = markX+lineX+caption.replaceAll("(?m)^", "    ");
73
74
        AttributedString as = new AttributedString(text);
75
        as.addAttribute(TextAttribute.FAMILY, Font.SANS_SERIF);
76
        as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, markX.length(), text.indexOf('\n', markX.length()));
77
78
        as.addAttribute(TextAttribute.SIZE, 15, 0, markX.length());
79
        as.addAttribute(TextAttribute.SIZE, 12, markX.length(), text.length());
80
81
        gfx.setColor(Color.BLACK);
82
83
        AttributedCharacterIterator chIter = as.getIterator();
84
        FontRenderContext frc = gfx.getFontRenderContext();
85
        LineBreakMeasurer measurer = new LineBreakMeasurer(chIter, frc);
86
        float y = 80, x = 5;
87
        for (int lineNr = 0; measurer.getPosition() < chIter.getEndIndex(); lineNr++) {
88
            int mpos = measurer.getPosition();
89
            int limit = text.indexOf('\n', mpos);
90
            limit = (limit == -1) ? text.length() : limit+1;
91
            TextLayout textLayout = measurer.nextLayout(bi.getWidth()-10, limit, false);
92
            if (lineNr != 1) {
93
                y += textLayout.getAscent();
94
            }
95
            textLayout.draw(gfx, x, y);
96
            y += textLayout.getDescent() + textLayout.getLeading();
97
        }
98
99
        if (inputImage != null) {
100
            FileMagic fm = FileMagic.valueOf(inputImage);
101
            String contentType;
102
            switch (fm) {
103
                case GIF:
104
                    contentType = PictureType.GIF.contentType;
105
                    break;
106
                case PNG:
107
                    contentType = PictureType.PNG.contentType;
108
                    break;
109
                case JPEG:
110
                    contentType = PictureType.JPEG.contentType;
111
                    break;
112
                case XML:
113
                    contentType = PictureType.SVG.contentType;
114
                    break;
115
                case EMF:
116
                    contentType = PictureType.EMF.contentType;
117
                    break;
118
                case WMF:
119
                    contentType = PictureType.WMF.contentType;
120
                    break;
121
                default:
122
                    throw new RuntimeException("unknown image type");
123
            }
124
125
            ImageRenderer renderer = DrawPictureShape.getImageRenderer(gfx, contentType);
126
127
            renderer.loadImage(inputImage, contentType);
128
129
            double targetX = 10;
130
            double targetY = 100;
131
            double targetWidth = bi.getWidth() - targetX;
132
            double targetHeight = targetY - 5;
133
            Dimension2D dim = renderer.getDimension();
134
            double scale = Math.min(targetWidth / dim.getWidth(), targetHeight / dim.getHeight());
135
            double effWidth = dim.getWidth() * scale;
136
            double effHeight = dim.getHeight() * scale;
137
138
            renderer.drawImage(gfx, new Rectangle2D.Double(targetX + ((bi.getWidth() - effWidth) / 2), targetY - effHeight, effWidth, effHeight));
139
        }
140
141
        if (invalidText != null) {
142
            gfx.setFont(new Font("Lucida Bright", Font.ITALIC, 60));
143
            gfx.rotate(Math.toRadians(-15), bi.getWidth()/2., bi.getHeight()/2.);
144
            TextLayout tl = new TextLayout(invalidText, gfx.getFont(), gfx.getFontRenderContext());
145
            Rectangle2D bounds = tl.getBounds();
146
            x = (float)((bi.getWidth()-bounds.getWidth())/2 - bounds.getX());
147
            y = (float)((bi.getHeight()-bounds.getHeight())/2 - bounds.getY());
148
            Shape outline = tl.getOutline(AffineTransform.getTranslateInstance(x+2, y+1));
149
            gfx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
150
            gfx.setPaint(Color.RED);
151
            gfx.draw(outline);
152
            gfx.setPaint(new GradientPaint(0, 0, Color.RED, 30, 20, new Color(128, 128, 255), true));
153
            tl.draw(gfx, x, y);
154
        }
155
156
        gfx.dispose();
157
158
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
159
        ImageIO.write(bi, "PNG", bos);
160
        return bos.toByteArray();
161
    }
162
}
(-)src/ooxml/testcases/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java (+50 lines)
Lines 98-105 Link Here
98
import org.apache.poi.util.LocaleUtil;
98
import org.apache.poi.util.LocaleUtil;
99
import org.apache.poi.util.POILogFactory;
99
import org.apache.poi.util.POILogFactory;
100
import org.apache.poi.util.POILogger;
100
import org.apache.poi.util.POILogger;
101
import org.apache.poi.util.TempFile;
101
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
102
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
102
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
103
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
104
import org.apache.poi.xwpf.usermodel.XWPFDocument;
105
import org.apache.poi.xwpf.usermodel.XWPFSignatureLine;
103
import org.apache.xmlbeans.SystemProperties;
106
import org.apache.xmlbeans.SystemProperties;
104
import org.apache.xmlbeans.XmlObject;
107
import org.apache.xmlbeans.XmlObject;
105
import org.bouncycastle.asn1.DEROctetString;
108
import org.bouncycastle.asn1.DEROctetString;
Lines 855-860 Link Here
855
        assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod());
858
        assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod());
856
    }
859
    }
857
860
861
    @Test
862
    public void testSignatureImage() throws Exception {
863
        File signDoc = TempFile.createTempFile("visual-signature",".docx");
864
        XWPFSignatureLine line = new XWPFSignatureLine();
865
        line.setSuggestedSigner("Jack Sparrow");
866
        line.setSuggestedSigner2("Captain");
867
        line.setSuggestedSignerEmail("jack.bl@ck.perl");
868
869
        try (XWPFDocument doc = new XWPFDocument();
870
             FileOutputStream fos = new FileOutputStream(signDoc)) {
871
            line.addSignatureLine(doc);
872
            doc.write(fos);
873
        }
874
875
        initKeyPair();
876
        try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ_WRITE)) {
877
            SignatureConfig sic = new SignatureConfig();
878
            sic.setKey(keyPair.getPrivate());
879
            sic.setSigningCertificateChain(Collections.singletonList(x509));
880
881
            byte[] plainSign = testdata.readFile("jack-sign.emf");
882
            String caption = line.getSuggestedSigner()+"\n"+line.getSuggestedSigner2()+"\n"+line.getSuggestedSignerEmail();
883
            byte[] signValid = SignatureImage.generateImage(caption, plainSign, null);
884
            byte[] signInvalid = SignatureImage.generateImage(caption, plainSign, "Bungling!");
885
886
            sic.setSignatureImage(line.getSetupId(), plainSign, signValid, signInvalid);
887
            sic.setDigestAlgo(HashAlgorithm.sha1);
888
            SignatureInfo si = new SignatureInfo();
889
            si.setOpcPackage(pkg);
890
            si.setSignatureConfig(sic);
891
            // hash > sha1 doesn't work in excel viewer ...
892
            si.confirmSignature();
893
        }
894
895
        try (OPCPackage pkg = OPCPackage.open(signDoc, PackageAccess.READ)) {
896
            XWPFDocument doc = new XWPFDocument(pkg);
897
            XWPFSignatureLine line2 = new XWPFSignatureLine();
898
            line2.parseSignatureLine(doc);
899
900
            assertEquals(line.getSuggestedSigner(), line2.getSuggestedSigner());
901
            assertEquals(line.getSuggestedSigner2(), line2.getSuggestedSigner2());
902
            assertEquals(line.getSuggestedSignerEmail(), line2.getSuggestedSignerEmail());
903
904
            pkg.revert();
905
        }
906
    }
907
858
    private SignatureConfig prepareConfig(String pfxInput) throws Exception {
908
    private SignatureConfig prepareConfig(String pfxInput) throws Exception {
859
        initKeyPair(pfxInput);
909
        initKeyPair(pfxInput);
860
910
(-)src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java (-1 / +2 lines)
Lines 19-24 Link Here
19
19
20
package org.apache.poi.xslf.model;
20
package org.apache.poi.xslf.model;
21
21
22
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
22
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.DML_NS;
23
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.DML_NS;
23
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.PML_NS;
24
import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.PML_NS;
24
25
Lines 37-43 Link Here
37
    public boolean fetch(XSLFShape shape) {
38
    public boolean fetch(XSLFShape shape) {
38
        CTTextBodyProperties props = null;
39
        CTTextBodyProperties props = null;
39
        try {
40
        try {
40
            props = shape.selectProperty(
41
            props = selectProperty(shape.getXmlObject(),
41
                    CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
42
                    CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
42
            return (props != null) && fetch(props);
43
            return (props != null) && fetch(props);
43
        } catch (XmlException e) {
44
        } catch (XmlException e) {
(-)src/ooxml/java/org/apache/poi/ooxml/util/XPathHelper.java (-5 / +182 lines)
Lines 17-30 Link Here
17
17
18
package org.apache.poi.ooxml.util;
18
package org.apache.poi.ooxml.util;
19
19
20
import org.apache.poi.util.POILogFactory;
20
import java.util.Locale;
21
import org.apache.poi.util.POILogger;
22
21
23
import javax.xml.XMLConstants;
22
import javax.xml.XMLConstants;
23
import javax.xml.namespace.QName;
24
import javax.xml.xpath.XPathFactory;
24
import javax.xml.xpath.XPathFactory;
25
25
26
import com.microsoft.schemas.compatibility.AlternateContentDocument;
27
import org.apache.poi.util.Internal;
28
import org.apache.poi.util.POILogFactory;
29
import org.apache.poi.util.POILogger;
30
import org.apache.poi.xslf.usermodel.XSLFShape;
31
import org.apache.xmlbeans.XmlCursor;
32
import org.apache.xmlbeans.XmlException;
33
import org.apache.xmlbeans.XmlObject;
34
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
35
26
public final class XPathHelper {
36
public final class XPathHelper {
27
    private static POILogger logger = POILogFactory.getLogger(XPathHelper.class);
37
    private static final POILogger LOG = POILogFactory.getLogger(XPathHelper.class);
38
39
    private static final String OSGI_ERROR =
40
        "Schemas (*.xsb) for <CLASS> can't be loaded - usually this happens when OSGI " +
41
        "loading is used and the thread context classloader has no reference to " +
42
        "the xmlbeans classes - please either verify if the <XSB>.xsb is on the " +
43
        "classpath or alternatively try to use the full ooxml-schemas-x.x.jar";
44
45
    private static final String MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
46
    private static final String MAC_DML_NS = "http://schemas.microsoft.com/office/mac/drawingml/2008/main";
47
    private static final QName ALTERNATE_CONTENT_TAG = new QName(MC_NS, "AlternateContent");
48
     // AlternateContentDocument.AlternateContent.type.getName();
28
49
29
    private XPathHelper() {}
50
    private XPathHelper() {}
30
51
Lines 41-49 Link Here
41
        try {
62
        try {
42
            xpf.setFeature(feature, enabled);
63
            xpf.setFeature(feature, enabled);
43
        } catch (Exception e) {
64
        } catch (Exception e) {
44
            logger.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
65
            LOG.log(POILogger.WARN, "XPathFactory Feature unsupported", feature, e);
45
        } catch (AbstractMethodError ame) {
66
        } catch (AbstractMethodError ame) {
46
            logger.log(POILogger.WARN, "Cannot set XPathFactory feature because outdated XML parser in classpath", feature, ame);
67
            LOG.log(POILogger.WARN, "Cannot set XPathFactory feature because outdated XML parser in classpath", feature, ame);
47
        }
68
        }
48
    }
69
    }
70
71
72
73
    /**
74
     * Internal code - API may change any time!
75
     * <p>
76
     * The {@link #selectProperty(Class, String)} xquery method has some performance penalties,
77
     * which can be workaround by using {@link XmlCursor}. This method also takes into account
78
     * that {@code AlternateContent} tags can occur anywhere on the given path.
79
     * <p>
80
     * It returns the first element found - the search order is:
81
     * <ul>
82
     *     <li>searching for a direct child</li>
83
     *     <li>searching for a AlternateContent.Choice child</li>
84
     *     <li>searching for a AlternateContent.Fallback child</li>
85
     * </ul>
86
     * Currently POI OOXML is based on the first edition of the ECMA 376 schema, which doesn't
87
     * allow AlternateContent tags to show up everywhere. The factory flag is
88
     * a workaround to process files based on a later edition. But it comes with the drawback:
89
     * any change on the returned XmlObject aren't saved back to the underlying document -
90
     * so it's a non updatable clone. If factory is null, a XmlException is
91
     * thrown if the AlternateContent is not allowed by the surrounding element or if the
92
     * extracted object is of the generic type XmlAnyTypeImpl.
93
     *
94
     * @param resultClass the requested result class
95
     * @param factory a factory parse method reference to allow reparsing of elements
96
     *                extracted from AlternateContent elements. Usually the enclosing XmlBeans type needs to be used
97
     *                to parse the stream
98
     * @param path the elements path, each array must contain at least 1 QName,
99
     *             but can contain additional alternative tags
100
     * @return the xml object at the path location, or null if not found
101
     *
102
     * @throws XmlException If factory is null, a XmlException is
103
     *      thrown if the AlternateContent is not allowed by the surrounding element or if the
104
     *      extracted object is of the generic type XmlAnyTypeImpl.
105
     *
106
     * @since POI 4.1.2
107
     */
108
    @SuppressWarnings("unchecked")
109
    @Internal
110
    public static <T extends XmlObject> T selectProperty(XmlObject startObject, Class<T> resultClass, XSLFShape.ReparseFactory<T> factory, QName[]... path)
111
            throws XmlException {
112
        XmlObject xo = startObject;
113
        XmlCursor cur = xo.newCursor();
114
        XmlCursor innerCur = null;
115
        try {
116
            innerCur = selectProperty(cur, path, 0, factory != null, false);
117
            if (innerCur == null) {
118
                return null;
119
            }
120
121
            // Pesky XmlBeans bug - see Bugzilla #49934
122
            // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
123
            xo = innerCur.getObject();
124
            if (xo instanceof XmlAnyTypeImpl) {
125
                String errorTxt = OSGI_ERROR
126
                        .replace("<CLASS>", resultClass.getSimpleName())
127
                        .replace("<XSB>", resultClass.getSimpleName().toLowerCase(Locale.ROOT)+"*");
128
                if (factory == null) {
129
                    throw new XmlException(errorTxt);
130
                } else {
131
                    xo = factory.parse(innerCur.newXMLStreamReader());
132
                }
133
            }
134
135
            return (T)xo;
136
        } finally {
137
            cur.dispose();
138
            if (innerCur != null) {
139
                innerCur.dispose();
140
            }
141
        }
142
    }
143
144
    private static XmlCursor selectProperty(final XmlCursor cur, final QName[][] path, final int offset, final boolean reparseAlternate, final boolean isAlternate)
145
            throws XmlException {
146
        // first try the direct children
147
        for (QName qn : path[offset]) {
148
            for (boolean found = cur.toChild(qn); found; found = cur.toNextSibling(qn)) {
149
                if (offset == path.length-1) {
150
                    return cur;
151
                }
152
                cur.push();
153
                XmlCursor innerCur = selectProperty(cur, path, offset+1, reparseAlternate, false);
154
                if (innerCur != null) {
155
                    return innerCur;
156
                }
157
                cur.pop();
158
            }
159
        }
160
        // if we were called inside an alternate content handling don't look for alternates again
161
        if (isAlternate || !cur.toChild(ALTERNATE_CONTENT_TAG)) {
162
            return null;
163
        }
164
165
        // otherwise check first the choice then the fallback content
166
        XmlObject xo = cur.getObject();
167
        AlternateContentDocument.AlternateContent alterCont;
168
        if (xo instanceof AlternateContentDocument.AlternateContent) {
169
            alterCont = (AlternateContentDocument.AlternateContent)xo;
170
        } else {
171
            // Pesky XmlBeans bug - see Bugzilla #49934
172
            // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
173
            if (!reparseAlternate) {
174
                throw new XmlException(OSGI_ERROR
175
                                               .replace("<CLASS>", "AlternateContent")
176
                                               .replace("<XSB>", "alternatecontentelement")
177
                );
178
            }
179
            try {
180
                AlternateContentDocument acd = AlternateContentDocument.Factory.parse(cur.newXMLStreamReader());
181
                alterCont = acd.getAlternateContent();
182
            } catch (XmlException e) {
183
                throw new XmlException("unable to parse AlternateContent element", e);
184
            }
185
        }
186
187
        final int choices = alterCont.sizeOfChoiceArray();
188
        for (int i=0; i<choices; i++) {
189
            // TODO: check [Requires] attribute of [Choice] element, if we can handle the content
190
            AlternateContentDocument.AlternateContent.Choice choice = alterCont.getChoiceArray(i);
191
            XmlCursor cCur = choice.newCursor();
192
            XmlCursor innerCur = null;
193
            try {
194
                String requiresNS = cCur.namespaceForPrefix(choice.getRequires());
195
                if (MAC_DML_NS.equalsIgnoreCase(requiresNS)) {
196
                    // Mac DML usually contains PDFs ...
197
                    continue;
198
                }
199
                innerCur = selectProperty(cCur, path, offset, reparseAlternate, true);
200
                if (innerCur != null) {
201
                    return innerCur;
202
                }
203
            } finally {
204
                if (innerCur != cCur) {
205
                    cCur.dispose();
206
                }
207
            }
208
        }
209
210
        if (!alterCont.isSetFallback()) {
211
            return null;
212
        }
213
214
        XmlCursor fCur = alterCont.getFallback().newCursor();
215
        XmlCursor innerCur = null;
216
        try {
217
            innerCur = selectProperty(fCur, path, offset, reparseAlternate, true);
218
            return innerCur;
219
        } finally {
220
            if (innerCur != fCur) {
221
                fCur.dispose();
222
            }
223
        }
224
    }
225
49
}
226
}
(-)src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java (-3 / +4 lines)
Lines 30-35 Link Here
30
import org.apache.poi.hpsf.ClassID;
30
import org.apache.poi.hpsf.ClassID;
31
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
31
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
32
import org.apache.poi.ooxml.POIXMLException;
32
import org.apache.poi.ooxml.POIXMLException;
33
import org.apache.poi.ooxml.util.XPathHelper;
33
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
34
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
34
import org.apache.poi.openxml4j.opc.OPCPackage;
35
import org.apache.poi.openxml4j.opc.OPCPackage;
35
import org.apache.poi.openxml4j.opc.PackagePart;
36
import org.apache.poi.openxml4j.opc.PackagePart;
Lines 76-82 Link Here
76
        // select oleObj potentially under AlternateContent
77
        // select oleObj potentially under AlternateContent
77
        // usually the mc:Choice element will be selected first
78
        // usually the mc:Choice element will be selected first
78
        try {
79
        try {
79
            _oleObject = selectProperty(CTOleObject.class, null, GRAPHIC, GRAPHIC_DATA, OLE_OBJ);
80
            _oleObject = XPathHelper.selectProperty(getXmlObject(), CTOleObject.class, null, GRAPHIC, GRAPHIC_DATA, OLE_OBJ);
80
        } catch (XmlException e) {
81
        } catch (XmlException e) {
81
            // ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
82
            // ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
82
            throw new IllegalStateException(e);
83
            throw new IllegalStateException(e);
Lines 146-153 Link Here
146
147
147
    protected CTBlipFillProperties getBlipFill() {
148
    protected CTBlipFillProperties getBlipFill() {
148
        try {
149
        try {
149
            CTPicture pic = selectProperty
150
            CTPicture pic = XPathHelper.selectProperty
150
                (CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
151
                (getXmlObject(), CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
151
            return (pic != null) ? pic.getBlipFill() : null;
152
            return (pic != null) ? pic.getBlipFill() : null;
152
        } catch (XmlException e) {
153
        } catch (XmlException e) {
153
            return null;
154
            return null;
(-)src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java (-1 / +2 lines)
Lines 35-40 Link Here
35
import javax.xml.namespace.QName;
35
import javax.xml.namespace.QName;
36
import javax.xml.stream.XMLStreamReader;
36
import javax.xml.stream.XMLStreamReader;
37
37
38
import org.apache.poi.ooxml.util.XPathHelper;
38
import org.apache.poi.openxml4j.opc.PackagePart;
39
import org.apache.poi.openxml4j.opc.PackagePart;
39
import org.apache.poi.openxml4j.opc.PackageRelationship;
40
import org.apache.poi.openxml4j.opc.PackageRelationship;
40
import org.apache.poi.sl.usermodel.PictureData;
41
import org.apache.poi.sl.usermodel.PictureData;
Lines 175-181 Link Here
175
        }
176
        }
176
177
177
        try {
178
        try {
178
            return selectProperty(CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
179
            return XPathHelper.selectProperty(getXmlObject(), CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
179
        } catch (XmlException xe) {
180
        } catch (XmlException xe) {
180
            return null;
181
            return null;
181
        }
182
        }
(-)src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java (-1 / +2 lines)
Lines 17-22 Link Here
17
17
18
package org.apache.poi.xslf.usermodel;
18
package org.apache.poi.xslf.usermodel;
19
19
20
import static org.apache.poi.ooxml.util.XPathHelper.selectProperty;
20
import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS;
21
import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS;
21
22
22
import java.util.function.Consumer;
23
import java.util.function.Consumer;
Lines 220-226 Link Here
220
221
221
    private CTApplicationNonVisualDrawingProps getNvProps() {
222
    private CTApplicationNonVisualDrawingProps getNvProps() {
222
        try {
223
        try {
223
            return shape.selectProperty(CTApplicationNonVisualDrawingProps.class, null, NV_CONTAINER, NV_PROPS);
224
            return selectProperty(shape.getXmlObject(), CTApplicationNonVisualDrawingProps.class, null, NV_CONTAINER, NV_PROPS);
224
        } catch (XmlException e) {
225
        } catch (XmlException e) {
225
            return null;
226
            return null;
226
        }
227
        }
(-)src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java (-169 / +2 lines)
Lines 21-33 Link Here
21
21
22
import java.awt.Graphics2D;
22
import java.awt.Graphics2D;
23
import java.awt.geom.Rectangle2D;
23
import java.awt.geom.Rectangle2D;
24
import java.util.Locale;
25
24
26
import javax.xml.namespace.QName;
25
import javax.xml.namespace.QName;
27
import javax.xml.stream.XMLStreamReader;
26
import javax.xml.stream.XMLStreamReader;
28
27
29
import com.microsoft.schemas.compatibility.AlternateContentDocument;
28
import org.apache.poi.ooxml.util.XPathHelper;
30
import com.microsoft.schemas.compatibility.AlternateContentDocument.AlternateContent;
31
import org.apache.poi.openxml4j.opc.PackagePart;
29
import org.apache.poi.openxml4j.opc.PackagePart;
32
import org.apache.poi.sl.draw.DrawFactory;
30
import org.apache.poi.sl.draw.DrawFactory;
33
import org.apache.poi.sl.draw.DrawPaint;
31
import org.apache.poi.sl.draw.DrawPaint;
Lines 45-51 Link Here
45
import org.apache.xmlbeans.XmlCursor;
43
import org.apache.xmlbeans.XmlCursor;
46
import org.apache.xmlbeans.XmlException;
44
import org.apache.xmlbeans.XmlException;
47
import org.apache.xmlbeans.XmlObject;
45
import org.apache.xmlbeans.XmlObject;
48
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
49
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
46
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
50
import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
47
import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
51
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
48
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
Lines 76-85 Link Here
76
73
77
    static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
74
    static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
78
    static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
75
    static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
79
    private static final String MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
80
    private static final String MAC_DML_NS = "http://schemas.microsoft.com/office/mac/drawingml/2008/main";
81
82
    private static final QName ALTERNATE_CONTENT_TAG = new QName(MC_NS, "AlternateContent");
83
76
84
    private static final QName[] NV_CONTAINER = {
77
    private static final QName[] NV_CONTAINER = {
85
        new QName(PML_NS, "nvSpPr"),
78
        new QName(PML_NS, "nvSpPr"),
Lines 93-104 Link Here
93
        new QName(PML_NS, "cNvPr")
86
        new QName(PML_NS, "cNvPr")
94
    };
87
    };
95
88
96
    private static final String OSGI_ERROR =
97
        "Schemas (*.xsb) for <CLASS> can't be loaded - usually this happens when OSGI " +
98
        "loading is used and the thread context classloader has no reference to " +
99
        "the xmlbeans classes - please either verify if the <XSB>.xsb is on the " +
100
        "classpath or alternatively try to use the full ooxml-schemas-x.x.jar";
101
102
    private final XmlObject _shape;
89
    private final XmlObject _shape;
103
    private final XSLFSheet _sheet;
90
    private final XSLFSheet _sheet;
104
    private XSLFShapeContainer _parent;
91
    private XSLFShapeContainer _parent;
Lines 239-245 Link Here
239
    protected CTNonVisualDrawingProps getCNvPr() {
226
    protected CTNonVisualDrawingProps getCNvPr() {
240
        try {
227
        try {
241
            if (_nvPr == null) {
228
            if (_nvPr == null) {
242
                _nvPr = selectProperty(CTNonVisualDrawingProps.class, null, NV_CONTAINER, CNV_PROPS);
229
                _nvPr = XPathHelper.selectProperty(getXmlObject(), CTNonVisualDrawingProps.class, null, NV_CONTAINER, CNV_PROPS);
243
            }
230
            }
244
            return _nvPr;
231
            return _nvPr;
245
        } catch (XmlException e) {
232
        } catch (XmlException e) {
Lines 322-481 Link Here
322
        return (resultClass.isInstance(rs[0])) ? (T)rs[0] : null;
309
        return (resultClass.isInstance(rs[0])) ? (T)rs[0] : null;
323
    }
310
    }
324
311
325
    /**
326
     * Internal code - API may change any time!
327
     * <p>
328
     * The {@link #selectProperty(Class, String)} xquery method has some performance penalties,
329
     * which can be workaround by using {@link XmlCursor}. This method also takes into account
330
     * that {@code AlternateContent} tags can occur anywhere on the given path.
331
     * <p>
332
     * It returns the first element found - the search order is:
333
     * <ul>
334
     *     <li>searching for a direct child</li>
335
     *     <li>searching for a AlternateContent.Choice child</li>
336
     *     <li>searching for a AlternateContent.Fallback child</li>
337
     * </ul>
338
     * Currently POI OOXML is based on the first edition of the ECMA 376 schema, which doesn't
339
     * allow AlternateContent tags to show up everywhere. The factory flag is
340
     * a workaround to process files based on a later edition. But it comes with the drawback:
341
     * any change on the returned XmlObject aren't saved back to the underlying document -
342
     * so it's a non updatable clone. If factory is null, a XmlException is
343
     * thrown if the AlternateContent is not allowed by the surrounding element or if the
344
     * extracted object is of the generic type XmlAnyTypeImpl.
345
     *
346
     * @param resultClass the requested result class
347
     * @param factory a factory parse method reference to allow reparsing of elements
348
     *                extracted from AlternateContent elements. Usually the enclosing XmlBeans type needs to be used
349
     *                to parse the stream
350
     * @param path the elements path, each array must contain at least 1 QName,
351
     *             but can contain additional alternative tags
352
     * @return the xml object at the path location, or null if not found
353
     *
354
     * @throws XmlException If factory is null, a XmlException is
355
     *      thrown if the AlternateContent is not allowed by the surrounding element or if the
356
     *      extracted object is of the generic type XmlAnyTypeImpl.
357
     *
358
     * @since POI 4.1.2
359
     */
360
    @SuppressWarnings("unchecked")
361
    @Internal
362
    public <T extends XmlObject> T selectProperty(Class<T> resultClass, ReparseFactory<T> factory, QName[]... path)
363
    throws XmlException {
364
        XmlObject xo = getXmlObject();
365
        XmlCursor cur = xo.newCursor();
366
        XmlCursor innerCur = null;
367
        try {
368
            innerCur = selectProperty(cur, path, 0, factory != null, false);
369
            if (innerCur == null) {
370
                return null;
371
            }
372
373
            // Pesky XmlBeans bug - see Bugzilla #49934
374
            // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
375
            xo = innerCur.getObject();
376
            if (xo instanceof XmlAnyTypeImpl) {
377
                String errorTxt = OSGI_ERROR
378
                    .replace("<CLASS>", resultClass.getSimpleName())
379
                    .replace("<XSB>", resultClass.getSimpleName().toLowerCase(Locale.ROOT)+"*");
380
                if (factory == null) {
381
                    throw new XmlException(errorTxt);
382
                } else {
383
                    xo = factory.parse(innerCur.newXMLStreamReader());
384
                }
385
            }
386
387
            return (T)xo;
388
        } finally {
389
            cur.dispose();
390
            if (innerCur != null) {
391
                innerCur.dispose();
392
            }
393
        }
394
    }
395
396
    private XmlCursor selectProperty(final XmlCursor cur, final QName[][] path, final int offset, final boolean reparseAlternate, final boolean isAlternate)
397
    throws XmlException {
398
        // first try the direct children
399
        for (QName qn : path[offset]) {
400
            if (cur.toChild(qn)) {
401
                if (offset == path.length-1) {
402
                    return cur;
403
                }
404
                cur.push();
405
                XmlCursor innerCur = selectProperty(cur, path, offset+1, reparseAlternate, false);
406
                if (innerCur != null) {
407
                    return innerCur;
408
                }
409
                cur.pop();
410
            }
411
        }
412
        // if we were called inside an alternate content handling don't look for alternates again
413
        if (isAlternate || !cur.toChild(ALTERNATE_CONTENT_TAG)) {
414
            return null;
415
        }
416
417
        // otherwise check first the choice then the fallback content
418
        XmlObject xo = cur.getObject();
419
        AlternateContent alterCont;
420
        if (xo instanceof AlternateContent) {
421
            alterCont = (AlternateContent)xo;
422
        } else {
423
            // Pesky XmlBeans bug - see Bugzilla #49934
424
            // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
425
            if (!reparseAlternate) {
426
                throw new XmlException(OSGI_ERROR
427
                    .replace("<CLASS>", "AlternateContent")
428
                    .replace("<XSB>", "alternatecontentelement")
429
                );
430
            }
431
            try {
432
                AlternateContentDocument acd = AlternateContentDocument.Factory.parse(cur.newXMLStreamReader());
433
                alterCont = acd.getAlternateContent();
434
            } catch (XmlException e) {
435
                throw new XmlException("unable to parse AlternateContent element", e);
436
            }
437
        }
438
439
        final int choices = alterCont.sizeOfChoiceArray();
440
        for (int i=0; i<choices; i++) {
441
            // TODO: check [Requires] attribute of [Choice] element, if we can handle the content
442
            AlternateContent.Choice choice = alterCont.getChoiceArray(i);
443
            XmlCursor cCur = choice.newCursor();
444
            XmlCursor innerCur = null;
445
            try {
446
                String requiresNS = cCur.namespaceForPrefix(choice.getRequires());
447
                if (MAC_DML_NS.equalsIgnoreCase(requiresNS)) {
448
                    // Mac DML usually contains PDFs ...
449
                    continue;
450
                }
451
                innerCur = selectProperty(cCur, path, offset, reparseAlternate, true);
452
                if (innerCur != null) {
453
                    return innerCur;
454
                }
455
            } finally {
456
                if (innerCur != cCur) {
457
                    cCur.dispose();
458
                }
459
            }
460
        }
461
462
        if (!alterCont.isSetFallback()) {
463
            return null;
464
        }
465
466
        XmlCursor fCur = alterCont.getFallback().newCursor();
467
        XmlCursor innerCur = null;
468
        try {
469
            innerCur = selectProperty(fCur, path, offset, reparseAlternate, true);
470
            return innerCur;
471
        } finally {
472
            if (innerCur != fCur) {
473
                fCur.dispose();
474
            }
475
        }
476
    }
477
478
479
    /**
312
    /**
480
     * Walk up the inheritance tree and fetch shape properties.<p>
313
     * Walk up the inheritance tree and fetch shape properties.<p>
481
     *
314
     *
(-)src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFSignatureLine.java (+179 lines)
Line 0 Link Here
1
/* ====================================================================
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
4
   contributor license agreements.  See the NOTICE file distributed with
5
   this work for additional information regarding copyright ownership.
6
   The ASF licenses this file to You under the Apache License, Version 2.0
7
   (the "License"); you may not use this file except in compliance with
8
   the License.  You may obtain a copy of the License at
9
10
       http://www.apache.org/licenses/LICENSE-2.0
11
12
   Unless required by applicable law or agreed to in writing, software
13
   distributed under the License is distributed on an "AS IS" BASIS,
14
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
   See the License for the specific language governing permissions and
16
   limitations under the License.
17
==================================================================== */
18
19
package org.apache.poi.xwpf.usermodel;
20
21
import java.io.IOException;
22
import java.util.UUID;
23
24
import javax.xml.namespace.QName;
25
26
import com.microsoft.schemas.office.office.CTSignatureLine;
27
import com.microsoft.schemas.office.office.STTrueFalse;
28
import com.microsoft.schemas.vml.CTGroup;
29
import com.microsoft.schemas.vml.CTImageData;
30
import com.microsoft.schemas.vml.CTShape;
31
import com.microsoft.schemas.vml.STExt;
32
import org.apache.poi.hpsf.ClassID;
33
import org.apache.poi.ooxml.POIXMLException;
34
import org.apache.poi.ooxml.util.XPathHelper;
35
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
36
import org.apache.poi.poifs.crypt.dsig.SignatureImage;
37
import org.apache.xmlbeans.XmlCursor;
38
import org.apache.xmlbeans.XmlException;
39
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPicture;
40
41
public class XWPFSignatureLine {
42
    static final String NS_OOXML_WP_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
43
    private static final String MS_VML_URN = "urn:schemas-microsoft-com:vml";
44
    private static final String MS_OFFICE_URN = "urn:schemas-microsoft-com:office:office";
45
46
    private ClassID setupId;
47
    private Boolean allowComments;
48
    private String signingInstructions = "Before signing the document, verify that the content you are signing is correct.";
49
    private String suggestedSigner;
50
    private String suggestedSigner2;
51
    private String suggestedSignerEmail;
52
53
    public ClassID getSetupId() {
54
        return setupId;
55
    }
56
57
    public void setSetupId(ClassID setupId) {
58
        this.setupId = setupId;
59
    }
60
61
    public Boolean getAllowComments() {
62
        return allowComments;
63
    }
64
65
    public void setAllowComments(Boolean allowComments) {
66
        this.allowComments = allowComments;
67
    }
68
69
    public String getSigningInstructions() {
70
        return signingInstructions;
71
    }
72
73
    public void setSigningInstructions(String signingInstructions) {
74
        this.signingInstructions = signingInstructions;
75
    }
76
77
    public String getSuggestedSigner() {
78
        return suggestedSigner;
79
    }
80
81
    public void setSuggestedSigner(String suggestedSigner) {
82
        this.suggestedSigner = suggestedSigner;
83
    }
84
85
    public String getSuggestedSigner2() {
86
        return suggestedSigner2;
87
    }
88
89
    public void setSuggestedSigner2(String suggestedSigner2) {
90
        this.suggestedSigner2 = suggestedSigner2;
91
    }
92
93
    public String getSuggestedSignerEmail() {
94
        return suggestedSignerEmail;
95
    }
96
97
    public void setSuggestedSignerEmail(String suggestedSignerEmail) {
98
        this.suggestedSignerEmail = suggestedSignerEmail;
99
    }
100
101
    public void parseSignatureLine(XWPFDocument doc) throws XmlException {
102
        CTSignatureLine line = XPathHelper.selectProperty(doc.getDocument(), CTSignatureLine.class, null,
103
            new QName[]{new QName(NS_OOXML_WP_MAIN, "body")},
104
            new QName[]{new QName(NS_OOXML_WP_MAIN, "p")},
105
            new QName[]{new QName(NS_OOXML_WP_MAIN, "r")},
106
            new QName[]{new QName(NS_OOXML_WP_MAIN, "pict")},
107
            new QName[]{new QName(MS_VML_URN, "shape")},
108
            new QName[]{new QName(MS_OFFICE_URN, "signatureline")});
109
        if (line == null) {
110
            return;
111
        }
112
        setupId = new ClassID(line.getId());
113
        allowComments = line.isSetAllowcomments() ? STTrueFalse.TRUE.equals(line.getAllowcomments()) : null;
114
        suggestedSigner = line.getSuggestedsigner();
115
        suggestedSigner2 = line.getSuggestedsigner2();
116
        suggestedSignerEmail = line.getSuggestedsigneremail();
117
        XmlCursor cur = line.newCursor();
118
        try {
119
            // the signinginstructions are actually qualified, but our schema version is too old
120
            signingInstructions = cur.getAttributeText(new QName(MS_OFFICE_URN, "signinginstructions"));
121
        } finally {
122
            cur.dispose();
123
        }
124
    }
125
126
    public void addSignatureLine(XWPFDocument document) {
127
        String caption = suggestedSigner+"\n"+suggestedSigner2+"\n"+suggestedSignerEmail;
128
        byte[] inputImage;
129
        try {
130
            inputImage = SignatureImage.generateImage(caption, null, null);
131
            XWPFRun r = document.createParagraph().createRun();
132
            CTPicture pict = r.getCTR().addNewPict();
133
134
            CTGroup grp = CTGroup.Factory.newInstance();
135
            CTShape shape = grp.addNewShape();
136
            shape.setAlt("Microsoft Office Signature Line...");
137
            shape.setStyle("width:191.95pt;height:96.05pt");
138
            shape.setType("rect");
139
140
            String relationId = document.addPictureData(inputImage, Document.PICTURE_TYPE_PNG);
141
            CTImageData imgData = shape.addNewImagedata();
142
            imgData.setId2(relationId);
143
            imgData.setTitle("");
144
145
            CTSignatureLine xsl = shape.addNewSignatureline();
146
            if (suggestedSigner != null) {
147
                xsl.setSuggestedsigner(suggestedSigner);
148
            }
149
            if (suggestedSigner2 != null) {
150
                xsl.setSuggestedsigner2(suggestedSigner2);
151
            }
152
            if (suggestedSignerEmail != null) {
153
                xsl.setSuggestedsigneremail(suggestedSignerEmail);
154
            }
155
            if (setupId == null) {
156
                setupId = new ClassID("{"+UUID.randomUUID().toString()+"}");
157
            }
158
            xsl.setId(setupId.toString());
159
            xsl.setAllowcomments(STTrueFalse.T);
160
            xsl.setIssignatureline(STTrueFalse.T);
161
            xsl.setProvid("{00000000-0000-0000-0000-000000000000}");
162
            xsl.setExt(STExt.EDIT);
163
            xsl.setSigninginstructionsset(STTrueFalse.T);
164
            XmlCursor cur = xsl.newCursor();
165
            cur.setAttributeText(new QName(MS_OFFICE_URN, "signinginstructions"), signingInstructions);
166
            cur.dispose();
167
168
            XmlCursor thisC = pict.newCursor();
169
            thisC.toEndToken();
170
            XmlCursor otherC = grp.newCursor();
171
            otherC.copyXmlContents(thisC);
172
            otherC.dispose();
173
            thisC.dispose();
174
        } catch (IOException | InvalidFormatException e) {
175
            // shouldn't happen ...
176
            throw new POIXMLException("Can't generate signature line image", e);
177
        }
178
    }
179
}

Return to bug 64773