/* * ==================================================================== * 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.xslf.usermodel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import javax.xml.namespace.QName; 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; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.poifs.filesystem.FileMagic; import org.apache.poi.poifs.filesystem.Ole10Native; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.sl.usermodel.ObjectMetaData; import org.apache.poi.sl.usermodel.ObjectMetaData.Application; import org.apache.poi.sl.usermodel.ObjectShape; import org.apache.poi.util.Internal; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; 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.CTShapeProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrameNonVisual; import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; import org.openxmlformats.schemas.presentationml.x2006.main.CTOleObject; import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; import org.openxmlformats.schemas.presentationml.x2006.main.CTPictureNonVisual; public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape { /* package */ static final String OLE_URI = "http://schemas.openxmlformats.org/presentationml/2006/ole"; private static final QName[] GRAPHIC = { new QName(DML_NS, "graphic") }; private static final QName[] GRAPHIC_DATA = { new QName(DML_NS, "graphicData") }; private static final QName[] OLE_OBJ = { new QName(PML_NS, "oleObj") }; private static final QName[] CT_PICTURE = { new QName(PML_NS, "pic") }; private CTOleObject _oleObject; private XSLFPictureData _data; /*package*/ XSLFObjectShape(CTGraphicalObjectFrame shape, XSLFSheet sheet){ super(shape, sheet); // select oleObj potentially under AlternateContent // usually the mc:Choice element will be selected first try { _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); } } @Internal public CTOleObject getCTOleObject(){ return _oleObject; } @Override public XSLFObjectData getObjectData() { String oleRel = getCTOleObject().getId(); return getSheet().getRelationPartById(oleRel).getDocumentPart(); } @Override public String getProgId() { return (_oleObject == null) ? null : _oleObject.getProgId(); } @Override public String getFullName() { return (_oleObject == null) ? null : _oleObject.getName(); } /** * Return the data on the (internal) picture. * For an external linked picture, will return null */ @Override public XSLFPictureData getPictureData() { if(_data == null){ String blipId = getBlipId(); if (blipId == null) { return null; } PackagePart p = getSheet().getPackagePart(); PackageRelationship rel = p.getRelationship(blipId); if (rel != null) { try { PackagePart imgPart = p.getRelatedPart(rel); _data = new XSLFPictureData(imgPart); } catch (Exception e) { throw new POIXMLException(e); } } } return _data; } protected CTBlip getBlip(){ return getBlipFill().getBlip(); } protected String getBlipId(){ String id = getBlip().getEmbed(); if (id.isEmpty()) { return null; } return id; } protected CTBlipFillProperties getBlipFill() { try { 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; } } private static CTPicture parse(XMLStreamReader reader) throws XmlException { CTGroupShape gs = CTGroupShape.Factory.parse(reader); return (gs.sizeOfPicArray() > 0) ? gs.getPicArray(0) : null; } @Override public OutputStream updateObjectData(final Application application, final ObjectMetaData metaData) throws IOException { final ObjectMetaData md = (application != null) ? application.getMetaData() : metaData; if (md == null || md.getClassID() == null) { throw new IllegalArgumentException("either application and/or metaData needs to be set."); } final XSLFSheet sheet = getSheet(); final RelationPart rp; if (_oleObject.isSetId()) { // object data was already set rp = sheet.getRelationPartById(_oleObject.getId()); } else { // object data needs to be initialized try { final XSLFRelation descriptor = XSLFRelation.OLE_OBJECT; final OPCPackage pack = sheet.getPackagePart().getPackage(); int nextIdx = pack.getUnusedPartIndex(descriptor.getDefaultFileName()); rp = sheet.createRelationship(descriptor, XSLFFactory.getInstance(), nextIdx, false); _oleObject.setId(rp.getRelationship().getId()); } catch (InvalidFormatException e) { throw new IOException("Unable to add new ole embedding", e); } // setting spid only works with a vml drawing object // oleObj.setSpid("_x0000_s"+(1025+objectIdx)); } _oleObject.setProgId(md.getProgId()); _oleObject.setName(md.getObjectName()); return new XSLFObjectOutputStream(rp.getDocumentPart().getPackagePart(),md); } private static class XSLFObjectOutputStream extends ByteArrayOutputStream { final PackagePart objectPart; final ObjectMetaData metaData; private XSLFObjectOutputStream(final PackagePart objectPart, final ObjectMetaData metaData) { super(100000); this.objectPart = objectPart; this.metaData = metaData; } public void close() throws IOException { objectPart.clear(); try (final OutputStream os = objectPart.getOutputStream()) { final ByteArrayInputStream bis = new ByteArrayInputStream(this.buf, 0, size()); final FileMagic fm = FileMagic.valueOf(this.buf); if (fm == FileMagic.OLE2) { try (final POIFSFileSystem poifs = new POIFSFileSystem(bis)) { poifs.getRoot().setStorageClsid(metaData.getClassID()); poifs.writeFilesystem(os); } } else if (metaData.getOleEntry() == null) { // OLE Name hasn't been specified, pass the input through os.write(this.buf, 0, size()); } else { try (final POIFSFileSystem poifs = new POIFSFileSystem()) { final ClassID clsId = metaData.getClassID(); if (clsId != null) { poifs.getRoot().setStorageClsid(clsId); } poifs.createDocument(bis, metaData.getOleEntry()); Ole10Native.createOleMarkerEntry(poifs); poifs.writeFilesystem(os); } } } } } /** * * * @param shapeId 1-based shapeId * @param picRel relationship to the picture data in the ooxml package * @return */ static CTGraphicalObjectFrame prototype(int shapeId, String picRel){ CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance(); CTGraphicalObjectFrameNonVisual nvGr = frame.addNewNvGraphicFramePr(); CTNonVisualDrawingProps cnv = nvGr.addNewCNvPr(); // usually the shape name has its index based on the n-th embeding, but having // the prototype separate from the actual updating of the object, we use the shape id cnv.setName("Object " + shapeId); cnv.setId(shapeId); // add empty property elements otherwise Powerpoint doesn't load the file ... nvGr.addNewCNvGraphicFramePr(); nvGr.addNewNvPr(); frame.addNewXfrm(); CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData(); gr.setUri(OLE_URI); XmlCursor grCur = gr.newCursor(); grCur.toEndToken(); grCur.beginElement(new QName(PML_NS, "oleObj")); grCur.insertElement(new QName(PML_NS, "embed")); CTGroupShape grpShp = CTGroupShape.Factory.newInstance(); CTPicture pic = grpShp.addNewPic(); CTPictureNonVisual nvPicPr = pic.addNewNvPicPr(); CTNonVisualDrawingProps cNvPr = nvPicPr.addNewCNvPr(); cNvPr.setName(""); cNvPr.setId(0); nvPicPr.addNewCNvPicPr(); nvPicPr.addNewNvPr(); CTBlipFillProperties blip = pic.addNewBlipFill(); blip.addNewBlip().setEmbed(picRel); blip.addNewStretch().addNewFillRect(); CTShapeProperties spPr = pic.addNewSpPr(); CTTransform2D xfrm = spPr.addNewXfrm(); CTPoint2D off = xfrm.addNewOff(); off.setX(1270000); off.setY(1270000); CTPositiveSize2D xext = xfrm.addNewExt(); xext.setCx(1270000); xext.setCy(1270000); spPr.addNewPrstGeom().setPrst(STShapeType.RECT); XmlCursor picCur = grpShp.newCursor(); picCur.toStartDoc(); picCur.moveXmlContents(grCur); picCur.dispose(); grCur.dispose(); return frame; } }