/* * ==================================================================== * 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 == null || 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); if (tc != null) { 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); if (tc == null) { continue; } 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); } } } }