123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- /* ====================================================================
- 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.xssf.usermodel;
-
- import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
-
- import javax.xml.namespace.QName;
-
- import org.apache.poi.POIXMLDocumentPart;
- import org.apache.poi.openxml4j.opc.PackagePart;
- import org.apache.poi.openxml4j.opc.PackageRelationship;
- import org.apache.poi.ss.usermodel.ClientAnchor;
- import org.apache.poi.ss.usermodel.Drawing;
- import org.apache.poi.ss.util.CellAddress;
- import org.apache.poi.ss.util.ImageUtils;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
- import org.apache.poi.util.Units;
- import org.apache.poi.xssf.model.CommentsTable;
- import org.apache.xmlbeans.XmlCursor;
- 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.CTGroupTransform2D;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
- import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTConnector;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrame;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGroupShape;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
- import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTOneCellAnchor;
- 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;
-
- /**
- * Represents a SpreadsheetML drawing
- */
- public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing<XSSFShape> {
- 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;
-
- /**
- * Create a new SpreadsheetML drawing
- *
- * @see org.apache.poi.xssf.usermodel.XSSFSheet#createDrawingPatriarch()
- */
- protected XSSFDrawing() {
- super();
- drawing = newDrawing();
- }
-
- /**
- * Construct a SpreadsheetML drawing from a package part
- *
- * @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
- */
- public XSSFDrawing(PackagePart part) throws IOException, XmlException {
- super(part);
- XmlOptions options = new XmlOptions(DEFAULT_XML_OPTIONS);
- //Removing root element
- options.setLoadReplaceDocumentElement(null);
- InputStream is = part.getInputStream();
- try {
- drawing = CTDrawing.Factory.parse(is,options);
- } finally {
- is.close();
- }
- }
-
- /**
- * Construct a new CTDrawing bean. By default, it's just an empty placeholder for drawing objects
- *
- * @return a new CTDrawing bean
- */
- private static CTDrawing newDrawing(){
- return CTDrawing.Factory.newInstance();
- }
-
- /**
- * Return the underlying CTDrawing bean, the root element of the SpreadsheetML Drawing part.
- *
- * @return the underlying CTDrawing bean
- */
- @Internal
- public CTDrawing getCTDrawing(){
- return drawing;
- }
-
- @Override
- protected void commit() throws IOException {
- XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
-
- /*
- Saved drawings must have the following namespaces set:
- <xdr:wsDr
- xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
- xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing">
- */
- xmlOptions.setSaveSyntheticDocumentElement(
- new QName(CTDrawing.type.getName().getNamespaceURI(), "wsDr", "xdr")
- );
-
- PackagePart part = getPackagePart();
- OutputStream out = part.getOutputStream();
- drawing.save(out, xmlOptions);
- out.close();
- }
-
- @Override
- public XSSFClientAnchor createAnchor(int dx1, int dy1, int dx2, int dy2,
- int col1, int row1, int col2, int row2) {
- return new XSSFClientAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2);
- }
-
- /**
- * Constructs a textbox under the drawing.
- *
- * @param anchor the client anchor describes how this group is attached
- * to the sheet.
- * @return the newly created textbox.
- */
- public XSSFTextBox createTextbox(XSSFClientAnchor anchor){
- long shapeId = newShapeId();
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTShape ctShape = ctAnchor.addNewSp();
- ctShape.set(XSSFSimpleShape.prototype());
- ctShape.getNvSpPr().getCNvPr().setId(shapeId);
- XSSFTextBox shape = new XSSFTextBox(this, ctShape);
- shape.anchor = anchor;
- return shape;
-
- }
-
- /**
- * Creates a picture.
- *
- * @param anchor the client anchor describes how this picture is attached to the sheet.
- * @param pictureIndex the index of the picture in the workbook collection of pictures,
- * {@link org.apache.poi.xssf.usermodel.XSSFWorkbook#getAllPictures()} .
- *
- * @return the newly created picture shape.
- */
- public XSSFPicture createPicture(XSSFClientAnchor anchor, int pictureIndex)
- {
- PackageRelationship rel = addPictureReference(pictureIndex);
-
- long shapeId = newShapeId();
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTPicture ctShape = ctAnchor.addNewPic();
- ctShape.set(XSSFPicture.prototype());
-
- ctShape.getNvPicPr().getCNvPr().setId(shapeId);
-
- XSSFPicture shape = new XSSFPicture(this, ctShape);
- shape.anchor = anchor;
- shape.setPictureReference(rel);
- ctShape.getSpPr().setXfrm(createXfrm(anchor));
-
- return shape;
- }
-
- @Override
- public XSSFPicture createPicture(ClientAnchor anchor, int pictureIndex){
- return createPicture((XSSFClientAnchor)anchor, pictureIndex);
- }
-
- /**
- * Creates a chart.
- * @param anchor the client anchor describes how this chart is attached to
- * the sheet.
- * @return the newly created chart
- * @see org.apache.poi.xssf.usermodel.XSSFDrawing#createChart(ClientAnchor)
- */
- public XSSFChart createChart(XSSFClientAnchor anchor) {
- int chartNumber = getPackagePart().getPackage().
- getPartsByContentType(XSSFRelation.CHART.getContentType()).size() + 1;
-
- RelationPart rp = createRelationship(
- XSSFRelation.CHART, XSSFFactory.getInstance(), chartNumber, false);
- XSSFChart chart = rp.getDocumentPart();
- String chartRelId = rp.getRelationship().getId();
-
- XSSFGraphicFrame frame = createGraphicFrame(anchor);
- frame.setChart(chart, chartRelId);
- frame.getCTGraphicalObjectFrame().setXfrm(createXfrm(anchor));
-
- return chart;
- }
-
- @Override
- public XSSFChart createChart(ClientAnchor anchor) {
- return createChart((XSSFClientAnchor)anchor);
- }
-
- /**
- * Add the indexed picture to this drawing relations
- *
- * @param pictureIndex the index of the picture in the workbook collection of pictures,
- * {@link org.apache.poi.xssf.usermodel.XSSFWorkbook#getAllPictures()} .
- */
- @SuppressWarnings("resource")
- protected PackageRelationship addPictureReference(int pictureIndex){
- XSSFWorkbook wb = (XSSFWorkbook)getParent().getParent();
- XSSFPictureData data = wb.getAllPictures().get(pictureIndex);
- XSSFPictureData pic = new XSSFPictureData(data.getPackagePart());
- RelationPart rp = addRelation(null, XSSFRelation.IMAGES, pic);
- return rp.getRelationship();
- }
-
- /**
- * Creates a simple shape. This includes such shapes as lines, rectangles,
- * and ovals.
- *
- * @param anchor the client anchor describes how this group is attached
- * to the sheet.
- * @return the newly created shape.
- */
- public XSSFSimpleShape createSimpleShape(XSSFClientAnchor anchor)
- {
- long shapeId = newShapeId();
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTShape ctShape = ctAnchor.addNewSp();
- ctShape.set(XSSFSimpleShape.prototype());
- ctShape.getNvSpPr().getCNvPr().setId(shapeId);
- ctShape.getSpPr().setXfrm(createXfrm(anchor));
- XSSFSimpleShape shape = new XSSFSimpleShape(this, ctShape);
- shape.anchor = anchor;
- return shape;
- }
-
- /**
- * Creates a simple shape. This includes such shapes as lines, rectangles,
- * and ovals.
- *
- * @param anchor the client anchor describes how this group is attached
- * to the sheet.
- * @return the newly created shape.
- */
- public XSSFConnector createConnector(XSSFClientAnchor anchor)
- {
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTConnector ctShape = ctAnchor.addNewCxnSp();
- ctShape.set(XSSFConnector.prototype());
-
- XSSFConnector shape = new XSSFConnector(this, ctShape);
- shape.anchor = anchor;
- return shape;
- }
-
- /**
- * Creates a simple shape. This includes such shapes as lines, rectangles,
- * and ovals.
- *
- * @param anchor the client anchor describes how this group is attached
- * to the sheet.
- * @return the newly created shape.
- */
- public XSSFShapeGroup createGroup(XSSFClientAnchor anchor)
- {
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTGroupShape ctGroup = ctAnchor.addNewGrpSp();
- ctGroup.set(XSSFShapeGroup.prototype());
- CTTransform2D xfrm = createXfrm(anchor);
- CTGroupTransform2D grpXfrm =ctGroup.getGrpSpPr().getXfrm();
- grpXfrm.setOff(xfrm.getOff());
- grpXfrm.setExt(xfrm.getExt());
- grpXfrm.setChExt(xfrm.getExt());
-
- XSSFShapeGroup shape = new XSSFShapeGroup(this, ctGroup);
- shape.anchor = anchor;
- return shape;
- }
-
- /**
- * Creates a comment.
- * @param anchor the client anchor describes how this comment is attached
- * to the sheet.
- * @return the newly created comment.
- */
- @Override
- public XSSFComment createCellComment(ClientAnchor anchor) {
- XSSFClientAnchor ca = (XSSFClientAnchor)anchor;
- XSSFSheet sheet = (XSSFSheet)getParent();
-
- //create comments and vmlDrawing parts if they don't exist
- CommentsTable comments = sheet.getCommentsTable(true);
- XSSFVMLDrawing vml = sheet.getVMLDrawing(true);
- com.microsoft.schemas.vml.CTShape vmlShape = vml.newCommentShape();
- if(ca.isSet()){
- // convert offsets from emus to pixels since we get a DrawingML-anchor
- // but create a VML Drawing
- int dx1Pixels = ca.getDx1()/Units.EMU_PER_PIXEL;
- int dy1Pixels = ca.getDy1()/Units.EMU_PER_PIXEL;
- int dx2Pixels = ca.getDx2()/Units.EMU_PER_PIXEL;
- int dy2Pixels = ca.getDy2()/Units.EMU_PER_PIXEL;
- String position =
- ca.getCol1() + ", " + dx1Pixels + ", " +
- ca.getRow1() + ", " + dy1Pixels + ", " +
- ca.getCol2() + ", " + dx2Pixels + ", " +
- ca.getRow2() + ", " + dy2Pixels;
- vmlShape.getClientDataArray(0).setAnchorArray(0, position);
- }
- CellAddress ref = new CellAddress(ca.getRow1(), ca.getCol1());
-
- if(comments.findCellComment(ref) != null) {
- throw new IllegalArgumentException("Multiple cell comments in one cell are not allowed, cell: " + ref);
- }
-
- return new XSSFComment(comments, comments.newComment(ref), vmlShape);
- }
-
- /**
- * Creates a new graphic frame.
- *
- * @param anchor the client anchor describes how this frame is attached
- * to the sheet
- * @return the newly created graphic frame
- */
- private XSSFGraphicFrame createGraphicFrame(XSSFClientAnchor anchor) {
- CTTwoCellAnchor ctAnchor = createTwoCellAnchor(anchor);
- CTGraphicalObjectFrame ctGraphicFrame = ctAnchor.addNewGraphicFrame();
- ctGraphicFrame.set(XSSFGraphicFrame.prototype());
- ctGraphicFrame.setXfrm(createXfrm(anchor));
-
- long frameId = numOfGraphicFrames++;
- XSSFGraphicFrame graphicFrame = new XSSFGraphicFrame(this, ctGraphicFrame);
- graphicFrame.setAnchor(anchor);
- graphicFrame.setId(frameId);
- graphicFrame.setName("Diagramm" + frameId);
- return graphicFrame;
- }
-
- /**
- * Returns all charts in this drawing.
- */
- public List<XSSFChart> getCharts() {
- List<XSSFChart> charts = new ArrayList<XSSFChart>();
- for(POIXMLDocumentPart part : getRelations()) {
- if(part instanceof XSSFChart) {
- charts.add((XSSFChart)part);
- }
- }
- return charts;
- }
-
- /**
- * Create and initialize a CTTwoCellAnchor that anchors a shape against top-left and bottom-right cells.
- *
- * @return a new CTTwoCellAnchor
- */
- private CTTwoCellAnchor createTwoCellAnchor(XSSFClientAnchor anchor) {
- CTTwoCellAnchor ctAnchor = drawing.addNewTwoCellAnchor();
- ctAnchor.setFrom(anchor.getFrom());
- ctAnchor.setTo(anchor.getTo());
- ctAnchor.addNewClientData();
- anchor.setTo(ctAnchor.getTo());
- anchor.setFrom(ctAnchor.getFrom());
- STEditAs.Enum aditAs;
- switch(anchor.getAnchorType()) {
- case DONT_MOVE_AND_RESIZE: aditAs = STEditAs.ABSOLUTE; break;
- case MOVE_AND_RESIZE: aditAs = STEditAs.TWO_CELL; break;
- case MOVE_DONT_RESIZE: aditAs = STEditAs.ONE_CELL; break;
- default: aditAs = STEditAs.ONE_CELL;
- }
- ctAnchor.setEditAs(aditAs);
- return ctAnchor;
- }
-
- private CTTransform2D createXfrm(XSSFClientAnchor anchor) {
- CTTransform2D xfrm = CTTransform2D.Factory.newInstance();
- CTPoint2D off = xfrm.addNewOff();
- off.setX(anchor.getDx1());
- off.setY(anchor.getDy1());
- XSSFSheet sheet = (XSSFSheet)getParent();
- double widthPx = 0;
- for (int col=anchor.getCol1(); col<anchor.getCol2(); col++) {
- widthPx += sheet.getColumnWidthInPixels(col);
- }
- double heightPx = 0;
- for (int row=anchor.getRow1(); row<anchor.getRow2(); row++) {
- heightPx += ImageUtils.getRowHeightInPixels(sheet, row);
- }
- int width = Units.pixelToEMU((int)widthPx);
- int height = Units.pixelToEMU((int)heightPx);
- CTPositiveSize2D ext = xfrm.addNewExt();
- ext.setCx(width - anchor.getDx1() + anchor.getDx2());
- ext.setCy(height - anchor.getDy1() + anchor.getDy2());
-
- // TODO: handle vflip/hflip
- return xfrm;
- }
-
- private long newShapeId(){
- return drawing.sizeOfTwoCellAnchorArray() + 1;
- }
-
- /**
- * @return list of shapes in this drawing
- */
- public List<XSSFShape> getShapes(){
- List<XSSFShape> lst = new ArrayList<XSSFShape>();
- XmlCursor cur = drawing.newCursor();
- try {
- if (cur.toFirstChild()) {
- addShapes(cur, lst);
- }
- } finally {
- cur.dispose();
- }
- return lst;
- }
-
- /**
- * @return list of shapes in this shape group
- */
- public List<XSSFShape> getShapes(XSSFShapeGroup groupshape){
- List<XSSFShape> lst = new ArrayList<XSSFShape>();
- XmlCursor cur = groupshape.getCTGroupShape().newCursor();
- try {
- addShapes(cur, lst);
- } finally {
- cur.dispose();
- }
- return lst;
- }
-
- private void addShapes(XmlCursor cur, List<XSSFShape> lst) {
- try {
- do {
- cur.push();
- if (cur.toFirstChild()) {
- do {
- XmlObject obj = cur.getObject();
-
- XSSFShape shape;
- if (obj instanceof CTMarker) {
- // ignore anchor elements
- continue;
- } else if (obj instanceof CTPicture) {
- shape = new XSSFPicture(this, (CTPicture)obj) ;
- } else if(obj instanceof CTConnector) {
- shape = new XSSFConnector(this, (CTConnector)obj) ;
- } else if(obj instanceof CTShape) {
- shape = hasOleLink(obj)
- ? new XSSFObjectData(this, (CTShape)obj)
- : new XSSFSimpleShape(this, (CTShape)obj) ;
- } else if(obj instanceof CTGraphicalObjectFrame) {
- shape = new XSSFGraphicFrame(this, (CTGraphicalObjectFrame)obj) ;
- } else if(obj instanceof CTGroupShape) {
- shape = new XSSFShapeGroup(this, (CTGroupShape)obj) ;
- } else if(obj instanceof XmlAnyTypeImpl) {
- LOG.log(POILogger.WARN, "trying to parse AlternateContent, "
- + "this unlinks the returned Shapes from the underlying xml content, "
- + "so those shapes can't be used to modify the drawing, "
- + "i.e. modifications will be ignored!");
-
- // XmlAnyTypeImpl is returned for AlternateContent parts, which might contain a CTDrawing
- cur.push();
- cur.toFirstChild();
- XmlCursor cur2 = null;
- try {
- // need to parse AlternateContent again, otherwise the child elements aren't typed,
- // but also XmlAnyTypes
- CTDrawing alterWS = CTDrawing.Factory.parse(cur.newXMLStreamReader());
- cur2 = alterWS.newCursor();
- if (cur2.toFirstChild()) {
- addShapes(cur2, lst);
- }
- } catch (XmlException e) {
- LOG.log(POILogger.WARN, "unable to parse CTDrawing in alternate content.", e);
- } finally {
- if (cur2 != null) {
- cur2.dispose();
- }
- cur.pop();
- }
- continue;
- } else {
- // ignore anything else
- continue;
- }
-
- assert(shape != null);
- shape.anchor = getAnchorFromParent(obj);
- lst.add(shape);
-
- } while (cur.toNextSibling());
- }
- cur.pop();
- } while (cur.toNextSibling());
- } finally {
- cur.dispose();
- }
- }
-
- private boolean hasOleLink(XmlObject shape) {
- QName uriName = new QName(null, "uri");
- String xquery = "declare namespace a='"+XSSFRelation.NS_DRAWINGML+"' .//a:extLst/a:ext";
- XmlCursor cur = shape.newCursor();
- cur.selectPath(xquery);
- try {
- while (cur.toNextSelection()) {
- String uri = cur.getAttributeText(uriName);
- if ("{63B3BB69-23CF-44E3-9099-C40C66FF867C}".equals(uri)) {
- return true;
- }
- }
- } finally {
- cur.dispose();
- }
- return false;
- }
-
- private XSSFAnchor getAnchorFromParent(XmlObject obj){
- XSSFAnchor anchor = null;
-
- XmlObject parentXbean = null;
- XmlCursor cursor = obj.newCursor();
- if(cursor.toParent()) {
- parentXbean = cursor.getObject();
- }
- cursor.dispose();
- if(parentXbean != null){
- if (parentXbean instanceof CTTwoCellAnchor) {
- CTTwoCellAnchor ct = (CTTwoCellAnchor)parentXbean;
- anchor = new XSSFClientAnchor(ct.getFrom(), ct.getTo());
- } else if (parentXbean instanceof CTOneCellAnchor) {
- CTOneCellAnchor ct = (CTOneCellAnchor)parentXbean;
- anchor = new XSSFClientAnchor(ct.getFrom(), CTMarker.Factory.newInstance());
- }
- }
- return anchor;
- }
-
- @Override
- public Iterator<XSSFShape> iterator() {
- return getShapes().iterator();
- }
- }
|