346 lines
12 KiB
Java
346 lines
12 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 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.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.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";
|
|
/* package */ static final String DRAWINGML_URI = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
|
|
private CTTable _table;
|
|
private List<XSLFTableRow> _rows;
|
|
|
|
/*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){
|
|
super(shape, sheet);
|
|
|
|
CTGraphicalObjectData god = shape.getGraphic().getGraphicData();
|
|
XmlCursor xc = god.newCursor();
|
|
if (!xc.toChild(DRAWINGML_URI, "tbl")) {
|
|
throw new IllegalStateException("a:tbl element was not found in\n " + god);
|
|
}
|
|
|
|
XmlObject xo = xc.getObject();
|
|
// 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 (xo instanceof XmlAnyTypeImpl){
|
|
String errStr =
|
|
"Schemas (*.xsb) for CTTable can't be loaded - usually this happens when OSGI " +
|
|
"loading is used and the thread context classloader has no reference to " +
|
|
"the xmlbeans classes - use POIXMLTypeLoader.setClassLoader() to set the loader, " +
|
|
"e.g. with CTTable.class.getClassLoader()"
|
|
;
|
|
throw new IllegalStateException(errStr);
|
|
}
|
|
_table = (CTTable)xo;
|
|
xc.dispose();
|
|
|
|
_rows = new ArrayList<XSLFTableRow>(_table.sizeOfTrArray());
|
|
for(CTTableRow row : _table.getTrArray()) {
|
|
_rows.add(new XSLFTableRow(row, this));
|
|
}
|
|
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 grCur = gr.newCursor();
|
|
grCur.toNextToken();
|
|
grCur.beginElement(new QName(DRAWINGML_URI, "tbl"));
|
|
|
|
CTTable tbl = CTTable.Factory.newInstance();
|
|
tbl.addNewTblPr();
|
|
tbl.addNewTblGrid();
|
|
XmlCursor tblCur = tbl.newCursor();
|
|
|
|
tblCur.moveXmlContents(grCur);
|
|
tblCur.dispose();
|
|
grCur.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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|