604 lines
20 KiB
Java
604 lines
20 KiB
Java
/* ====================================================================
|
|
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 static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
|
|
|
|
import java.awt.Graphics2D;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import javax.xml.namespace.QName;
|
|
|
|
import org.apache.poi.POIXMLDocumentPart;
|
|
import org.apache.poi.POIXMLException;
|
|
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.PackagePartName;
|
|
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
|
import org.apache.poi.openxml4j.opc.TargetMode;
|
|
import org.apache.poi.sl.draw.DrawFactory;
|
|
import org.apache.poi.sl.draw.DrawPictureShape;
|
|
import org.apache.poi.sl.draw.Drawable;
|
|
import org.apache.poi.sl.usermodel.PictureData;
|
|
import org.apache.poi.sl.usermodel.Sheet;
|
|
import org.apache.poi.sl.usermodel.SimpleShape.Placeholder;
|
|
import org.apache.poi.util.Beta;
|
|
import org.apache.poi.util.IOUtils;
|
|
import org.apache.poi.util.Internal;
|
|
import org.apache.xmlbeans.XmlObject;
|
|
import org.apache.xmlbeans.XmlOptions;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTCommonSlideData;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
|
|
import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
|
|
|
|
@Beta
|
|
public abstract class XSLFSheet extends POIXMLDocumentPart
|
|
implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
|
|
private XSLFCommonSlideData _commonSlideData;
|
|
private XSLFDrawing _drawing;
|
|
private List<XSLFShape> _shapes;
|
|
private CTGroupShape _spTree;
|
|
|
|
private List<XSLFTextShape>_placeholders;
|
|
private Map<Integer, XSLFSimpleShape> _placeholderByIdMap;
|
|
private Map<Integer, XSLFSimpleShape> _placeholderByTypeMap;
|
|
|
|
public XSLFSheet() {
|
|
super();
|
|
}
|
|
|
|
public XSLFSheet(PackagePart part, PackageRelationship rel){
|
|
super(part, rel);
|
|
}
|
|
|
|
/**
|
|
* @return the XMLSlideShow this sheet belongs to
|
|
*/
|
|
@Override
|
|
public XMLSlideShow getSlideShow() {
|
|
POIXMLDocumentPart p = getParent();
|
|
while(p != null) {
|
|
if(p instanceof XMLSlideShow){
|
|
return (XMLSlideShow)p;
|
|
}
|
|
p = p.getParent();
|
|
}
|
|
throw new IllegalStateException("SlideShow was not found");
|
|
}
|
|
|
|
protected static List<XSLFShape> buildShapes(CTGroupShape spTree, XSLFSheet sheet){
|
|
List<XSLFShape> shapes = new ArrayList<XSLFShape>();
|
|
for(XmlObject ch : spTree.selectPath("*")){
|
|
if(ch instanceof CTShape){ // simple shape
|
|
XSLFAutoShape shape = XSLFAutoShape.create((CTShape)ch, sheet);
|
|
shapes.add(shape);
|
|
} else if (ch instanceof CTGroupShape){
|
|
shapes.add(new XSLFGroupShape((CTGroupShape)ch, sheet));
|
|
} else if (ch instanceof CTConnector){
|
|
shapes.add(new XSLFConnectorShape((CTConnector)ch, sheet));
|
|
} else if (ch instanceof CTPicture){
|
|
shapes.add(new XSLFPictureShape((CTPicture)ch, sheet));
|
|
} else if (ch instanceof CTGraphicalObjectFrame){
|
|
XSLFGraphicFrame shape = XSLFGraphicFrame.create((CTGraphicalObjectFrame)ch, sheet);
|
|
shapes.add(shape);
|
|
}
|
|
}
|
|
return shapes;
|
|
}
|
|
|
|
/**
|
|
* @return top-level Xml bean representing this sheet
|
|
*/
|
|
public abstract XmlObject getXmlObject();
|
|
|
|
@Internal
|
|
public XSLFCommonSlideData getCommonSlideData() {
|
|
return _commonSlideData;
|
|
}
|
|
|
|
protected void setCommonSlideData(CTCommonSlideData data) {
|
|
if(data == null) {
|
|
_commonSlideData = null;
|
|
} else {
|
|
_commonSlideData = new XSLFCommonSlideData(data);
|
|
}
|
|
}
|
|
|
|
private XSLFDrawing getDrawing(){
|
|
initDrawingAndShapes();
|
|
return _drawing;
|
|
}
|
|
|
|
/**
|
|
* Returns an array containing all of the shapes in this sheet
|
|
*
|
|
* @return an array of all shapes in this sheet
|
|
*/
|
|
@Override
|
|
public List<XSLFShape> getShapes(){
|
|
initDrawingAndShapes();
|
|
return _shapes;
|
|
}
|
|
|
|
/**
|
|
* Helper method for initializing drawing and shapes in one go.
|
|
* If they are initialized separately, there's a risk that shapes
|
|
* get added twice, e.g. a shape is added to the drawing, then
|
|
* buildShapes is called and at last the shape is added to shape list
|
|
*/
|
|
private void initDrawingAndShapes() {
|
|
CTGroupShape cgs = getSpTree();
|
|
if(_drawing == null) {
|
|
_drawing = new XSLFDrawing(this, cgs);
|
|
}
|
|
if (_shapes == null) {
|
|
_shapes = buildShapes(cgs, this);
|
|
}
|
|
}
|
|
|
|
// shape factory methods
|
|
|
|
@Override
|
|
public XSLFAutoShape createAutoShape(){
|
|
XSLFAutoShape sh = getDrawing().createAutoShape();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFFreeformShape createFreeform(){
|
|
XSLFFreeformShape sh = getDrawing().createFreeform();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFTextBox createTextBox(){
|
|
XSLFTextBox sh = getDrawing().createTextBox();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFConnectorShape createConnector(){
|
|
XSLFConnectorShape sh = getDrawing().createConnector();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFGroupShape createGroup(){
|
|
XSLFGroupShape sh = getDrawing().createGroup();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFPictureShape createPicture(PictureData pictureData){
|
|
if (!(pictureData instanceof XSLFPictureData)) {
|
|
throw new IllegalArgumentException("pictureData needs to be of type XSLFPictureData");
|
|
}
|
|
XSLFPictureData xPictureData = (XSLFPictureData)pictureData;
|
|
PackagePart pic = xPictureData.getPackagePart();
|
|
|
|
PackageRelationship rel = getPackagePart().addRelationship(
|
|
pic.getPartName(), TargetMode.INTERNAL, XSLFRelation.IMAGES.getRelation());
|
|
addRelation(rel.getId(), new XSLFPictureData(pic, rel));
|
|
|
|
XSLFPictureShape sh = getDrawing().createPicture(rel.getId());
|
|
new DrawPictureShape(sh).resize();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
public XSLFTable createTable(){
|
|
XSLFTable sh = getDrawing().createTable();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
return sh;
|
|
}
|
|
|
|
@Override
|
|
public XSLFTable createTable(int numRows, int numCols){
|
|
if (numRows < 1 || numCols < 1) {
|
|
throw new IllegalArgumentException("numRows and numCols must be greater than 0");
|
|
}
|
|
XSLFTable sh = getDrawing().createTable();
|
|
getShapes().add(sh);
|
|
sh.setParent(this);
|
|
for (int r=0; r<numRows; r++) {
|
|
XSLFTableRow row = sh.addRow();
|
|
for (int c=0; c<numCols; c++) {
|
|
row.addCell();
|
|
}
|
|
}
|
|
return sh;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns an iterator over the shapes in this sheet
|
|
*
|
|
* @return an iterator over the shapes in this sheet
|
|
*/
|
|
public Iterator<XSLFShape> iterator(){
|
|
return getShapes().iterator();
|
|
}
|
|
|
|
public void addShape(XSLFShape shape) {
|
|
throw new UnsupportedOperationException(
|
|
"Adding a shape from a different container is not supported -"
|
|
+ " create it from scratch witht XSLFSheet.create* methods");
|
|
}
|
|
|
|
/**
|
|
* Removes the specified shape from this sheet, if it is present
|
|
* (optional operation). If this sheet does not contain the element,
|
|
* it is unchanged.
|
|
*
|
|
* @param xShape shape to be removed from this sheet, if present
|
|
* @return <tt>true</tt> if this sheet contained the specified element
|
|
* @throws IllegalArgumentException if the type of the specified shape
|
|
* is incompatible with this sheet (optional)
|
|
*/
|
|
public boolean removeShape(XSLFShape xShape) {
|
|
XmlObject obj = xShape.getXmlObject();
|
|
CTGroupShape spTree = getSpTree();
|
|
if(obj instanceof CTShape){
|
|
spTree.getSpList().remove(obj);
|
|
} else if (obj instanceof CTGroupShape) {
|
|
spTree.getGrpSpList().remove(obj);
|
|
} else if (obj instanceof CTConnector) {
|
|
spTree.getCxnSpList().remove(obj);
|
|
} else if (obj instanceof CTGraphicalObjectFrame) {
|
|
spTree.getGraphicFrameList().remove(obj);
|
|
} else if (obj instanceof CTPicture) {
|
|
XSLFPictureShape ps = (XSLFPictureShape)xShape;
|
|
removePictureRelation(ps);
|
|
spTree.getPicList().remove(obj);
|
|
} else {
|
|
throw new IllegalArgumentException("Unsupported shape: " + xShape);
|
|
}
|
|
return getShapes().remove(xShape);
|
|
}
|
|
|
|
/**
|
|
* Removes all of the elements from this container (optional operation).
|
|
* The container will be empty after this call returns.
|
|
*/
|
|
public void clear() {
|
|
List<XSLFShape> shapes = new ArrayList<XSLFShape>(getShapes());
|
|
for(XSLFShape shape : shapes){
|
|
removeShape(shape);
|
|
}
|
|
}
|
|
|
|
protected abstract String getRootElementName();
|
|
|
|
protected CTGroupShape getSpTree(){
|
|
if(_spTree == null) {
|
|
XmlObject root = getXmlObject();
|
|
XmlObject[] sp = root.selectPath(
|
|
"declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:spTree");
|
|
if(sp.length == 0) throw new IllegalStateException("CTGroupShape was not found");
|
|
_spTree = (CTGroupShape)sp[0];
|
|
}
|
|
return _spTree;
|
|
}
|
|
|
|
protected final void commit() throws IOException {
|
|
XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
|
|
String docName = getRootElementName();
|
|
if(docName != null) {
|
|
xmlOptions.setSaveSyntheticDocumentElement(
|
|
new QName("http://schemas.openxmlformats.org/presentationml/2006/main", docName));
|
|
}
|
|
|
|
PackagePart part = getPackagePart();
|
|
OutputStream out = part.getOutputStream();
|
|
getXmlObject().save(out, xmlOptions);
|
|
out.close();
|
|
}
|
|
|
|
/**
|
|
* Set the contents of this sheet to be a copy of the source sheet.
|
|
* This method erases any existing shapes and replaces them with
|
|
* object from the source sheet.
|
|
*
|
|
* @param src the source sheet to copy data from
|
|
* @return modified 'this'
|
|
*/
|
|
public XSLFSheet importContent(XSLFSheet src){
|
|
_shapes = null;
|
|
_spTree = null;
|
|
_drawing = null;
|
|
_spTree = null;
|
|
_placeholders = null;
|
|
|
|
// fix-me: wth would this ever happen to work ...
|
|
|
|
|
|
// first copy the source xml
|
|
getSpTree().set(src.getSpTree());
|
|
|
|
// recursively update each shape
|
|
List<XSLFShape> tgtShapes = getShapes();
|
|
List<XSLFShape> srcShapes = src.getShapes();
|
|
for(int i = 0; i < tgtShapes.size(); i++){
|
|
XSLFShape s1 = srcShapes.get(i);
|
|
XSLFShape s2 = tgtShapes.get(i);
|
|
|
|
s2.copy(s1);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Append content to this sheet.
|
|
*
|
|
* @param src the source sheet
|
|
* @return modified <code>this</code>.
|
|
*/
|
|
public XSLFSheet appendContent(XSLFSheet src){
|
|
CTGroupShape spTree = getSpTree();
|
|
int numShapes = getShapes().size();
|
|
|
|
CTGroupShape srcTree = src.getSpTree();
|
|
for(XmlObject ch : srcTree.selectPath("*")){
|
|
if(ch instanceof CTShape){ // simple shape
|
|
spTree.addNewSp().set(ch);
|
|
} else if (ch instanceof CTGroupShape){
|
|
spTree.addNewGrpSp().set(ch);
|
|
} else if (ch instanceof CTConnector){
|
|
spTree.addNewCxnSp().set(ch);
|
|
} else if (ch instanceof CTPicture){
|
|
spTree.addNewPic().set(ch);
|
|
} else if (ch instanceof CTGraphicalObjectFrame){
|
|
spTree.addNewGraphicFrame().set(ch);
|
|
}
|
|
}
|
|
|
|
_shapes = null;
|
|
_spTree = null;
|
|
_drawing = null;
|
|
_spTree = null;
|
|
_placeholders = null;
|
|
|
|
// recursively update each shape
|
|
List<XSLFShape> tgtShapes = getShapes();
|
|
List<XSLFShape> srcShapes = src.getShapes();
|
|
for(int i = 0; i < srcShapes.size(); i++){
|
|
XSLFShape s1 = srcShapes.get(i);
|
|
XSLFShape s2 = tgtShapes.get(numShapes + i);
|
|
|
|
s2.copy(s1);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* @return theme (shared styles) associated with this theme.
|
|
* By default returns <code>null</code> which means that this sheet is theme-less.
|
|
* Sheets that support the notion of themes (slides, masters, layouts, etc.) should override this
|
|
* method and return the corresponding package part.
|
|
*/
|
|
XSLFTheme getTheme(){
|
|
return null;
|
|
}
|
|
|
|
protected XSLFTextShape getTextShapeByType(Placeholder type){
|
|
for(XSLFShape shape : this.getShapes()){
|
|
if(shape instanceof XSLFTextShape) {
|
|
XSLFTextShape txt = (XSLFTextShape)shape;
|
|
if(txt.getTextType() == type) {
|
|
return txt;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
XSLFSimpleShape getPlaceholder(CTPlaceholder ph) {
|
|
XSLFSimpleShape shape = null;
|
|
if(ph.isSetIdx()) shape = getPlaceholderById((int)ph.getIdx());
|
|
|
|
if (shape == null && ph.isSetType()) {
|
|
shape = getPlaceholderByType(ph.getType().intValue());
|
|
}
|
|
return shape;
|
|
}
|
|
|
|
void initPlaceholders() {
|
|
if(_placeholders == null) {
|
|
_placeholders = new ArrayList<XSLFTextShape>();
|
|
_placeholderByIdMap = new HashMap<Integer, XSLFSimpleShape>();
|
|
_placeholderByTypeMap = new HashMap<Integer, XSLFSimpleShape>();
|
|
|
|
for(XSLFShape sh : getShapes()){
|
|
if(sh instanceof XSLFTextShape){
|
|
XSLFTextShape sShape = (XSLFTextShape)sh;
|
|
CTPlaceholder ph = sShape.getCTPlaceholder();
|
|
if(ph != null) {
|
|
_placeholders.add(sShape);
|
|
if(ph.isSetIdx()) {
|
|
int idx = (int)ph.getIdx();
|
|
_placeholderByIdMap.put(idx, sShape);
|
|
}
|
|
if(ph.isSetType()){
|
|
_placeholderByTypeMap.put(ph.getType().intValue(), sShape);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XSLFSimpleShape getPlaceholderById(int id) {
|
|
initPlaceholders();
|
|
return _placeholderByIdMap.get(id);
|
|
}
|
|
|
|
XSLFSimpleShape getPlaceholderByType(int ordinal) {
|
|
initPlaceholders();
|
|
return _placeholderByTypeMap.get(ordinal);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param idx 0-based index of a placeholder in the sheet
|
|
* @return placeholder
|
|
*/
|
|
public XSLFTextShape getPlaceholder(int idx) {
|
|
initPlaceholders();
|
|
return _placeholders.get(idx);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return all placeholder shapes in this sheet
|
|
*/
|
|
public XSLFTextShape[] getPlaceholders() {
|
|
initPlaceholders();
|
|
return _placeholders.toArray(new XSLFTextShape[_placeholders.size()]);
|
|
}
|
|
|
|
/**
|
|
* Checks if this <code>sheet</code> displays the specified shape.
|
|
*
|
|
* Subclasses can override it and skip certain shapes from drawings,
|
|
* for instance, slide masters and layouts don't display placeholders
|
|
*/
|
|
protected boolean canDraw(XSLFShape shape){
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return whether shapes on the master sheet should be shown. By default master graphics is turned off.
|
|
* Sheets that support the notion of master (slide, slideLayout) should override it and
|
|
* check this setting in the sheet XML
|
|
*/
|
|
public boolean getFollowMasterGraphics(){
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return background for this sheet
|
|
*/
|
|
@Override
|
|
public XSLFBackground getBackground() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Render this sheet into the supplied graphics object
|
|
*
|
|
* @param graphics
|
|
*/
|
|
@Override
|
|
public void draw(Graphics2D graphics){
|
|
DrawFactory drawFact = DrawFactory.getInstance(graphics);
|
|
Drawable draw = drawFact.getDrawable(this);
|
|
draw.draw(graphics);
|
|
}
|
|
|
|
/**
|
|
* Import a picture data from another document.
|
|
*
|
|
* @param blipId ID of the package relationship to retrieve.
|
|
* @param packagePart package part containing the data to import
|
|
* @return ID of the created relationship
|
|
*/
|
|
@SuppressWarnings("resource")
|
|
String importBlip(String blipId, PackagePart packagePart) {
|
|
PackageRelationship blipRel = packagePart.getRelationship(blipId);
|
|
PackagePart blipPart;
|
|
try {
|
|
blipPart = packagePart.getRelatedPart(blipRel);
|
|
} catch (InvalidFormatException e){
|
|
throw new POIXMLException(e);
|
|
}
|
|
XSLFPictureData data = new XSLFPictureData(blipPart, null);
|
|
|
|
XMLSlideShow ppt = getSlideShow();
|
|
XSLFPictureData pictureData = ppt.addPicture(data.getData(), data.getType());
|
|
PackagePart pic = pictureData.getPackagePart();
|
|
|
|
PackageRelationship rel = getPackagePart().addRelationship(
|
|
pic.getPartName(), TargetMode.INTERNAL, blipRel.getRelationshipType());
|
|
addRelation(rel.getId(), new XSLFPictureData(pic, rel));
|
|
|
|
return rel.getId();
|
|
}
|
|
|
|
/**
|
|
* Import a package part into this sheet.
|
|
*/
|
|
@SuppressWarnings("resource")
|
|
PackagePart importPart(PackageRelationship srcRel, PackagePart srcPafrt) {
|
|
PackagePart destPP = getPackagePart();
|
|
PackagePartName srcPPName = srcPafrt.getPartName();
|
|
|
|
OPCPackage pkg = destPP.getPackage();
|
|
if(pkg.containPart(srcPPName)){
|
|
// already exists
|
|
return pkg.getPart(srcPPName);
|
|
}
|
|
|
|
destPP.addRelationship(srcPPName, TargetMode.INTERNAL, srcRel.getRelationshipType());
|
|
|
|
PackagePart part = pkg.createPart(srcPPName, srcPafrt.getContentType());
|
|
try {
|
|
OutputStream out = part.getOutputStream();
|
|
InputStream is = srcPafrt.getInputStream();
|
|
IOUtils.copy(is, out);
|
|
is.close();
|
|
out.close();
|
|
} catch (IOException e){
|
|
throw new POIXMLException(e);
|
|
}
|
|
return part;
|
|
}
|
|
|
|
/**
|
|
* Helper method for sheet and group shapes
|
|
*
|
|
* @param pictureShape the picture shapes whose relation is to be removed
|
|
*/
|
|
void removePictureRelation(XSLFPictureShape pictureShape) {
|
|
POIXMLDocumentPart pd = getRelationById(pictureShape.getBlipId());
|
|
removeRelation(pd);
|
|
}
|
|
} |