/* | |
* ==================================================================== | |
* 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.geom.Rectangle2D; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.List; | |
import javax.xml.namespace.QName; | |
import org.apache.poi.POIXMLException; | |
import org.apache.poi.sl.draw.DrawFactory; | |
import org.apache.poi.sl.draw.DrawTableShape; | |
import org.apache.poi.sl.draw.DrawTextShape; | |
import org.apache.poi.sl.usermodel.TableShape; | |
import org.apache.poi.util.Internal; | |
import org.apache.poi.util.Units; | |
import org.apache.xmlbeans.XmlCursor; | |
import org.apache.xmlbeans.XmlException; | |
import org.apache.xmlbeans.XmlObject; | |
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTTable; | |
import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; | |
import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrameNonVisual; | |
/** | |
* Represents a table in a .pptx presentation | |
*/ | |
public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow>, | |
TableShape<XSLFShape,XSLFTextParagraph> { | |
/* package */ static final String TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table"; | |
private CTTable _table; | |
private List<XSLFTableRow> _rows; | |
/*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){ | |
super(shape, sheet); | |
XmlObject[] rs = shape.getGraphic().getGraphicData() | |
.selectPath("declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' ./a:tbl"); | |
if (rs.length == 0) { | |
throw new IllegalStateException("a:tbl element was not found in\n " + shape.getGraphic().getGraphicData()); | |
} | |
// Pesky XmlBeans bug - see Bugzilla #49934 | |
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas | |
if(rs[0] instanceof XmlAnyTypeImpl){ | |
try { | |
rs[0] = CTTable.Factory.parse(rs[0].toString(), DEFAULT_XML_OPTIONS); | |
}catch (XmlException e){ | |
throw new POIXMLException(e); | |
} | |
} | |
_table = (CTTable) rs[0]; | |
CTTableRow[] trArray = _table.getTrArray(); | |
_rows = new ArrayList<XSLFTableRow>(trArray.length); | |
for(CTTableRow row : trArray) { | |
XSLFTableRow xr = new XSLFTableRow(row, this); | |
_rows.add(xr); | |
} | |
updateRowColIndexes(); | |
} | |
@Override | |
public XSLFTableCell getCell(int row, int col) { | |
List<XSLFTableRow> rows = getRows(); | |
if (row < 0 || rows.size() <= row) { | |
return null; | |
} | |
XSLFTableRow r = rows.get(row); | |
if (r == null) { | |
// empty row | |
return null; | |
} | |
List<XSLFTableCell> cells = r.getCells(); | |
if (col < 0 || cells.size() <= col) { | |
return null; | |
} | |
// cell can be potentially empty ... | |
return cells.get(col); | |
} | |
@Internal | |
public CTTable getCTTable(){ | |
return _table; | |
} | |
@Override | |
public int getNumberOfColumns() { | |
return _table.getTblGrid().sizeOfGridColArray(); | |
} | |
@Override | |
public int getNumberOfRows() { | |
return _table.sizeOfTrArray(); | |
} | |
@Override | |
public double getColumnWidth(int idx){ | |
return Units.toPoints( | |
_table.getTblGrid().getGridColArray(idx).getW()); | |
} | |
@Override | |
public void setColumnWidth(int idx, double width) { | |
_table.getTblGrid().getGridColArray(idx).setW(Units.toEMU(width)); | |
} | |
@Override | |
public double getRowHeight(int row) { | |
return Units.toPoints(_table.getTrArray(row).getH()); | |
} | |
@Override | |
public void setRowHeight(int row, double height) { | |
_table.getTrArray(row).setH(Units.toEMU(height)); | |
} | |
public Iterator<XSLFTableRow> iterator(){ | |
return _rows.iterator(); | |
} | |
public List<XSLFTableRow> getRows(){ | |
return Collections.unmodifiableList(_rows); | |
} | |
public XSLFTableRow addRow(){ | |
CTTableRow tr = _table.addNewTr(); | |
XSLFTableRow row = new XSLFTableRow(tr, this); | |
// default height is 20 points | |
row.setHeight(20.0); | |
_rows.add(row); | |
updateRowColIndexes(); | |
return row; | |
} | |
static CTGraphicalObjectFrame prototype(int shapeId){ | |
CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance(); | |
CTGraphicalObjectFrameNonVisual nvGr = frame.addNewNvGraphicFramePr(); | |
CTNonVisualDrawingProps cnv = nvGr.addNewCNvPr(); | |
cnv.setName("Table " + shapeId); | |
cnv.setId(shapeId + 1); | |
nvGr.addNewCNvGraphicFramePr().addNewGraphicFrameLocks().setNoGrp(true); | |
nvGr.addNewNvPr(); | |
frame.addNewXfrm(); | |
CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData(); | |
XmlCursor cursor = gr.newCursor(); | |
cursor.toNextToken(); | |
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tbl")); | |
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblPr")); | |
cursor.toNextToken(); | |
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblGrid")); | |
cursor.dispose(); | |
gr.setUri(TABLE_URI); | |
return frame; | |
} | |
/** | |
* Merge cells of a table | |
*/ | |
public void mergeCells(int firstRow, int lastRow, int firstCol, int lastCol) { | |
if(firstRow > lastRow) { | |
throw new IllegalArgumentException( | |
"Cannot merge, first row > last row : " | |
+ firstRow + " > " + lastRow | |
); | |
} | |
if(firstCol > lastCol) { | |
throw new IllegalArgumentException( | |
"Cannot merge, first column > last column : " | |
+ firstCol + " > " + lastCol | |
); | |
} | |
int rowSpan = (lastRow - firstRow) + 1; | |
boolean mergeRowRequired = rowSpan > 1; | |
int colSpan = (lastCol - firstCol) + 1; | |
boolean mergeColumnRequired = colSpan > 1; | |
for(int i = firstRow; i <= lastRow; i++) { | |
XSLFTableRow row = _rows.get(i); | |
for(int colPos = firstCol; colPos <= lastCol; colPos++) { | |
XSLFTableCell cell = row.getCells().get(colPos); | |
if(mergeRowRequired) { | |
if(i == firstRow) { | |
cell.setRowSpan(rowSpan); | |
} else { | |
cell.setVMerge(true); | |
} | |
} | |
if(mergeColumnRequired) { | |
if(colPos == firstCol) { | |
cell.setGridSpan(colSpan); | |
} else { | |
cell.setHMerge(true); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Get assigned TableStyle | |
* | |
* @return the assigned TableStyle | |
* | |
* @since POI 3.15-beta2 | |
*/ | |
protected XSLFTableStyle getTableStyle() { | |
CTTable tab = getCTTable(); | |
// TODO: support inline table style | |
if (!tab.isSetTblPr() || !tab.getTblPr().isSetTableStyleId()) { | |
return null; | |
} | |
String styleId = tab.getTblPr().getTableStyleId(); | |
XSLFTableStyles styles = getSheet().getSlideShow().getTableStyles(); | |
for (XSLFTableStyle style : styles.getStyles()) { | |
if (style.getStyleId().equals(styleId)) { | |
return style; | |
} | |
} | |
return null; | |
} | |
/* package */ void updateRowColIndexes() { | |
int rowIdx = 0; | |
for (XSLFTableRow xr : this) { | |
int colIdx = 0; | |
for (XSLFTableCell tc : xr) { | |
tc.setRowColIndex(rowIdx, colIdx); | |
colIdx++; | |
} | |
rowIdx++; | |
} | |
} | |
/* package */ void updateCellAnchor() { | |
int rows = getNumberOfRows(); | |
int cols = getNumberOfColumns(); | |
double colWidths[] = new double[cols]; | |
double rowHeights[] = new double[rows]; | |
for (int row=0; row<rows; row++) { | |
rowHeights[row] = getRowHeight(row); | |
} | |
for (int col=0; col<cols; col++) { | |
colWidths[col] = getColumnWidth(col); | |
} | |
Rectangle2D tblAnc = getAnchor(); | |
DrawFactory df = DrawFactory.getInstance(null); | |
double newY = tblAnc.getY(); | |
// #1 pass - determine row heights, the height values might be too low or 0 ... | |
for (int row=0; row<rows; row++) { | |
double maxHeight = 0; | |
for (int col=0; col<cols; col++) { | |
XSLFTableCell tc = getCell(row, col); | |
if (tc.getGridSpan() != 1 || tc.getRowSpan() != 1) { | |
continue; | |
} | |
// need to set the anchor before height calculation | |
tc.setAnchor(new Rectangle2D.Double(0,0,colWidths[col],0)); | |
DrawTextShape dts = df.getDrawable(tc); | |
maxHeight = Math.max(maxHeight, dts.getTextHeight()); | |
} | |
rowHeights[row] = Math.max(rowHeights[row],maxHeight); | |
} | |
// #2 pass - init properties | |
for (int row=0; row<rows; row++) { | |
double newX = tblAnc.getX(); | |
for (int col=0; col<cols; col++) { | |
Rectangle2D bounds = new Rectangle2D.Double(newX, newY, colWidths[col], rowHeights[row]); | |
XSLFTableCell tc = getCell(row, col); | |
tc.setAnchor(bounds); | |
newX += colWidths[col]+DrawTableShape.borderSize; | |
} | |
newY += rowHeights[row]+DrawTableShape.borderSize; | |
} | |
// #3 pass - update merge info | |
for (int row=0; row<rows; row++) { | |
for (int col=0; col<cols; col++) { | |
XSLFTableCell tc = getCell(row, col); | |
Rectangle2D mergedBounds = tc.getAnchor(); | |
for (int col2=col+1; col2<col+tc.getGridSpan(); col2++) { | |
assert(col2 < cols); | |
XSLFTableCell tc2 = getCell(row, col2); | |
assert(tc2.getGridSpan() == 1 && tc2.getRowSpan() == 1); | |
mergedBounds.add(tc2.getAnchor()); | |
} | |
for (int row2=row+1; row2<row+tc.getRowSpan(); row2++) { | |
assert(row2 < rows); | |
XSLFTableCell tc2 = getCell(row2, col); | |
assert(tc2.getGridSpan() == 1 && tc2.getRowSpan() == 1); | |
mergedBounds.add(tc2.getAnchor()); | |
} | |
tc.setAnchor(mergedBounds); | |
} | |
} | |
} | |
} |