diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index cc6805c5b..d7baeb58e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -48,6 +48,7 @@ import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.HexDump; import org.apache.poi.util.Internal; import org.apache.poi.util.NotImplemented; @@ -137,6 +138,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing getChildren() { return Collections.unmodifiableList(_shapes); } @@ -369,6 +366,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing copy = new ArrayList(_shapes); for (HSSFShape shape: copy){ @@ -469,6 +469,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing extends ShapeContainer { * @return the newly created client anchor */ ClientAnchor createAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2); + + /** + * Adds a new OLE Package Shape + * + * @param anchor the client anchor describes how this picture is + * attached to the sheet. + * @param storageId the storageId returned by {@link Workbook#addOlePackage(byte[], String, String, String)} + * @param pictureIndex the index of the picture (used as preview image) in the + * workbook collection of pictures. + * + * @return newly created shape + */ + ObjectData createObjectData(ClientAnchor anchor, int storageId, int pictureIndex); } diff --git a/src/java/org/apache/poi/ss/usermodel/Workbook.java b/src/java/org/apache/poi/ss/usermodel/Workbook.java index f5043b727..5273423de 100644 --- a/src/java/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/java/org/apache/poi/ss/usermodel/Workbook.java @@ -286,6 +286,7 @@ public interface Workbook extends Closeable, Iterable { * @return the font with the matched attributes or null * @deprecated POI 3.15 beta 2. Use {@link #findFont(boolean, short, short, String, boolean, boolean, short, byte)} instead. */ + @Deprecated Font findFont(short boldWeight, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline); /** @@ -635,4 +636,18 @@ public interface Workbook extends Closeable, Iterable { * @since 3.14 beta 2 */ SpreadsheetVersion getSpreadsheetVersion(); + + /** + * Adds an OLE package manager object with the given content to the sheet + * + * @param oleData the payload + * @param label the label of the payload + * @param fileName the original filename + * @param command the command to open the payload + * + * @return the index of the added ole object, i.e. the storage id + * + * @throws IOException if the object can't be embedded + */ + int addOlePackage(byte[] oleData, String label, String fileName, String command) throws IOException; } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java index 17f187102..3a645f767 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFDrawing.java @@ -23,6 +23,7 @@ import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Comment; import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.ObjectData; import org.apache.poi.xssf.usermodel.XSSFDrawing; import org.apache.poi.xssf.usermodel.XSSFPicture; import org.apache.poi.xssf.usermodel.XSSFShape; @@ -62,9 +63,15 @@ public class SXSSFDrawing implements Drawing { return _drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2); } + @Override + public ObjectData createObjectData(ClientAnchor anchor, int storageId, int pictureIndex) { + return _drawing.createObjectData(anchor, storageId, pictureIndex); + } + @Override public Iterator iterator() { return _drawing.getShapes().iterator(); } + } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java index a11bef310..611f16bc0 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java @@ -1391,5 +1391,10 @@ public class SXSSFWorkbook implements Workbook { return SpreadsheetVersion.EXCEL2007; } + @Override + public int addOlePackage(byte[] oleData, String label, String fileName, String command) throws IOException { + return _wb.addOlePackage(oleData, label, fileName, command); + } + //end of interface implementation } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java index 5c7395ed3..35f71381c 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java @@ -28,9 +28,16 @@ import java.util.List; import javax.xml.namespace.QName; +import org.apache.poi.POIXMLDocument; import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; +import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.util.CellAddress; @@ -45,7 +52,9 @@ import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; @@ -59,19 +68,22 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShape; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.STEditAs; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOleObject; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOleObjects; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; /** * Represents a SpreadsheetML drawing */ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing { private static final POILogger LOG = POILogFactory.getLogger(XSSFDrawing.class); - + /** * Root element of the SpreadsheetML Drawing part */ private CTDrawing drawing; private long numOfGraphicFrames = 0L; - + protected static final String NAMESPACE_A = XSSFRelation.NS_DRAWINGML; protected static final String NAMESPACE_C = XSSFRelation.NS_CHART; @@ -90,7 +102,7 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawingapplication/vnd.openxmlformats-officedocument.drawing+xml - * + * * @since POI 3.14-Beta1 */ public XSSFDrawing(PackagePart part) throws IOException, XmlException { @@ -105,7 +117,7 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing lst) { try { do { @@ -470,7 +560,7 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing iterator() { return getShapes().iterator(); } + + /** + * @return the sheet associated with the drawing + */ + public XSSFSheet getSheet() { + return (XSSFSheet)getParent(); + } + } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFObjectData.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFObjectData.java index ab51df81e..222197941 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFObjectData.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFObjectData.java @@ -36,7 +36,17 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.xmlbeans.XmlCursor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtension; +import org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeArtExtensionList; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShape; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTShapeNonVisual; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOleObject; /** @@ -56,16 +66,57 @@ public class XSSFObjectData extends XSSFSimpleShape implements ObjectData { super(drawing, ctShape); } + /** + * Prototype with the default structure of a new auto-shape. + */ /** * Prototype with the default structure of a new auto-shape. */ protected static CTShape prototype() { + final String drawNS = "http://schemas.microsoft.com/office/drawing/2010/main"; + if(prototype == null) { - prototype = XSSFSimpleShape.prototype(); + CTShape shape = CTShape.Factory.newInstance(); + + CTShapeNonVisual nv = shape.addNewNvSpPr(); + CTNonVisualDrawingProps nvp = nv.addNewCNvPr(); + nvp.setId(1); + nvp.setName("Shape 1"); +// nvp.setHidden(true); + CTOfficeArtExtensionList extLst = nvp.addNewExtLst(); + // https://msdn.microsoft.com/en-us/library/dd911027(v=office.12).aspx + CTOfficeArtExtension ext = extLst.addNewExt(); + ext.setUri("{63B3BB69-23CF-44E3-9099-C40C66FF867C}"); + XmlCursor cur = ext.newCursor(); + cur.toEndToken(); + cur.beginElement(new QName(drawNS, "compatExt", "a14")); + cur.insertNamespace("a14", drawNS); + cur.insertAttributeWithValue("spid", "_x0000_s1"); + cur.dispose(); + + nv.addNewCNvSpPr(); + + CTShapeProperties sp = shape.addNewSpPr(); + CTTransform2D t2d = sp.addNewXfrm(); + CTPositiveSize2D p1 = t2d.addNewExt(); + p1.setCx(0); + p1.setCy(0); + CTPoint2D p2 = t2d.addNewOff(); + p2.setX(0); + p2.setY(0); + + CTPresetGeometry2D geom = sp.addNewPrstGeom(); + geom.setPrst(STShapeType.RECT); + geom.addNewAvLst(); + + prototype = shape; } return prototype; } + + + @Override public String getOLE2ClassName() { return getOleObject().getProgId(); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index c6df00599..6aa2064c3 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -47,6 +47,7 @@ import org.apache.poi.POIXMLDocument; import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLException; import org.apache.poi.POIXMLProperties; +import org.apache.poi.hpsf.ClassID; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; @@ -58,6 +59,9 @@ import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; 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.filesystem.DirectoryNode; +import org.apache.poi.poifs.filesystem.Ole10Native; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.SheetNameFormatter; import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; @@ -2437,4 +2441,41 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } return null; } + + @Override + public int addOlePackage(byte[] oleData, String label, String fileName, String command) + throws IOException { + // find an unused part name + OPCPackage opc = getPackage(); + PackagePartName pnOLE; + int oleId=0; + do { + try { + pnOLE = PackagingURIHelper.createPartName( "/xl/embeddings/oleObject"+(++oleId)+".bin" ); + } catch (InvalidFormatException e) { + throw new IOException("ole object name not recognized", e); + } + } while (opc.containPart(pnOLE)); + + PackagePart pp = opc.createPart( pnOLE, "application/vnd.openxmlformats-officedocument.oleObject" ); + + Ole10Native ole10 = new Ole10Native(label, fileName, command, oleData); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(oleData.length+500); + ole10.writeOut(bos); + + POIFSFileSystem poifs = new POIFSFileSystem(); + DirectoryNode root = poifs.getRoot(); + root.createDocument(Ole10Native.OLE10_NATIVE, new ByteArrayInputStream(bos.toByteArray())); + root.setStorageClsid(ClassID.OLE10_PACKAGE); + + // TODO: generate CombObj stream + + OutputStream os = pp.getOutputStream(); + poifs.writeFilesystem(os); + os.close(); + poifs.close(); + + return oleId; + } } diff --git a/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java b/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java new file mode 100644 index 000000000..0664d64b1 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java @@ -0,0 +1,125 @@ +/* ==================================================================== + 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.ss.usermodel; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; + +import javax.imageio.ImageIO; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.sl.usermodel.AutoShape; +import org.apache.poi.sl.usermodel.ShapeType; +import org.apache.poi.sl.usermodel.Slide; +import org.apache.poi.sl.usermodel.SlideShow; +import org.apache.poi.ss.extractor.EmbeddedData; +import org.apache.poi.ss.extractor.EmbeddedExtractor; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xssf.XSSFTestDataSamples; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Test; + +public class TestEmbedOLEPackage { + @Test + public void embedXSSF() throws IOException { + Workbook wb1 = new XSSFWorkbook(); + Sheet sh = wb1.createSheet(); + int picIdx = wb1.addPicture(getSamplePng(), Workbook.PICTURE_TYPE_PNG); + byte samplePPTX[] = getSamplePPT(true); + int oleIdx = wb1.addOlePackage(samplePPTX, "dummy.pptx", "dummy.pptx", "dummy.pptx"); + + Drawing pat = sh.createDrawingPatriarch(); + ClientAnchor anchor = pat.createAnchor(0, 0, 0, 0, 1, 1, 3, 6); + pat.createObjectData(anchor, oleIdx, picIdx); + + Workbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1); + + pat = wb2.getSheetAt(0).getDrawingPatriarch(); + assertTrue(pat.iterator().next() instanceof ObjectData); + + EmbeddedExtractor ee = new EmbeddedExtractor(); + EmbeddedData ed = ee.extractAll(wb2.getSheetAt(0)).get(0); + assertArrayEquals(samplePPTX, ed.getEmbeddedData()); + + wb2.close(); + wb1.close(); + } + + @Test + public void embedHSSF() throws IOException { + try { + Class.forName("org.apache.poi.hslf.usermodel.HSLFSlideShow"); + } catch (Exception e) { + assumeTrue(false); + } + + Workbook wb1 = new HSSFWorkbook(); + Sheet sh = wb1.createSheet(); + int picIdx = wb1.addPicture(getSamplePng(), Workbook.PICTURE_TYPE_PNG); + byte samplePPT[] = getSamplePPT(false); + int oleIdx = wb1.addOlePackage(samplePPT, "dummy.ppt", "dummy.ppt", "dummy.ppt"); + + Drawing pat = sh.createDrawingPatriarch(); + ClientAnchor anchor = pat.createAnchor(0, 0, 0, 0, 1, 1, 3, 6); + pat.createObjectData(anchor, oleIdx, picIdx); + + Workbook wb2 = HSSFTestDataSamples.writeOutAndReadBack((HSSFWorkbook)wb1); + + pat = wb2.getSheetAt(0).getDrawingPatriarch(); + assertTrue(pat.iterator().next() instanceof ObjectData); + + EmbeddedExtractor ee = new EmbeddedExtractor(); + EmbeddedData ed = ee.extractAll(wb2.getSheetAt(0)).get(0); + assertArrayEquals(samplePPT, ed.getEmbeddedData()); + + wb2.close(); + wb1.close(); + } + + static byte[] getSamplePng() throws IOException { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + URL imgUrl = cl.getResource("javax/swing/plaf/metal/icons/ocean/directory.gif"); + BufferedImage img = ImageIO.read(imgUrl); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageIO.write(img, "PNG", bos); + return bos.toByteArray(); + } + + static byte[] getSamplePPT(boolean ooxml) throws IOException { + SlideShow ppt = (ooxml) ? new XMLSlideShow() : new org.apache.poi.hslf.usermodel.HSLFSlideShow(); + Slide slide = ppt.createSlide(); + + AutoShape sh1 = slide.createAutoShape(); + sh1.setShapeType(ShapeType.STAR_32); + sh1.setAnchor(new java.awt.Rectangle(50, 50, 100, 200)); + sh1.setFillColor(java.awt.Color.red); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ppt.write(bos); + ppt.close(); + + return bos.toByteArray(); + } +}