You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSLFTable.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.xslf.usermodel;
  20. import java.awt.geom.Rectangle2D;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import javax.xml.namespace.QName;
  26. import org.apache.poi.ooxml.util.POIXMLUnits;
  27. import org.apache.poi.sl.draw.DrawFactory;
  28. import org.apache.poi.sl.draw.DrawTableShape;
  29. import org.apache.poi.sl.draw.DrawTextShape;
  30. import org.apache.poi.sl.usermodel.TableShape;
  31. import org.apache.poi.util.Internal;
  32. import org.apache.poi.util.Units;
  33. import org.apache.poi.xddf.usermodel.text.XDDFTextBody;
  34. import org.apache.xmlbeans.XmlCursor;
  35. import org.apache.xmlbeans.XmlObject;
  36. import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
  37. import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
  38. import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.CTTable;
  40. import org.openxmlformats.schemas.drawingml.x2006.main.CTTableCol;
  41. import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow;
  42. import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame;
  43. import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrameNonVisual;
  44. /**
  45. * Represents a table in a .pptx presentation
  46. */
  47. public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow>,
  48. TableShape<XSLFShape,XSLFTextParagraph> {
  49. /* package */ static final String TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table";
  50. private final CTTable _table;
  51. private final List<XSLFTableRow> _rows;
  52. /*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){
  53. super(shape, sheet);
  54. CTGraphicalObjectData god = shape.getGraphic().getGraphicData();
  55. XmlCursor xc = god.newCursor();
  56. try {
  57. if (!xc.toChild(XSLFRelation.NS_DRAWINGML, "tbl")) {
  58. throw new IllegalStateException("a:tbl element was not found in\n " + god);
  59. }
  60. XmlObject xo = xc.getObject();
  61. // Pesky XmlBeans bug - see Bugzilla #49934
  62. // it never happens when using poi-ooxml-full jar but may happen with the abridged poi-ooxml-lite jar
  63. if (xo instanceof XmlAnyTypeImpl){
  64. String errStr =
  65. "Schemas (*.xsb) for CTTable can't be loaded - usually this happens when OSGI " +
  66. "loading is used and the thread context classloader has no reference to " +
  67. "the xmlbeans classes"
  68. ;
  69. throw new IllegalStateException(errStr);
  70. }
  71. _table = (CTTable)xo;
  72. } finally {
  73. xc.dispose();
  74. }
  75. _rows = new ArrayList<>(_table.sizeOfTrArray());
  76. for(CTTableRow row : _table.getTrList()) {
  77. _rows.add(new XSLFTableRow(row, this));
  78. }
  79. updateRowColIndexes();
  80. }
  81. @Override
  82. public XSLFTableCell getCell(int row, int col) {
  83. if (row < 0 || _rows.size() <= row) {
  84. return null;
  85. }
  86. XSLFTableRow r = _rows.get(row);
  87. if (r == null) {
  88. // empty row
  89. return null;
  90. }
  91. List<XSLFTableCell> cells = r.getCells();
  92. if (col < 0 || cells.size() <= col) {
  93. return null;
  94. }
  95. // cell can be potentially empty ...
  96. return cells.get(col);
  97. }
  98. @Internal
  99. public CTTable getCTTable(){
  100. return _table;
  101. }
  102. @Override
  103. public int getNumberOfColumns() {
  104. return _table.getTblGrid().sizeOfGridColArray();
  105. }
  106. @Override
  107. public int getNumberOfRows() {
  108. return _table.sizeOfTrArray();
  109. }
  110. @Override
  111. public double getColumnWidth(int idx){
  112. return Units.toPoints(POIXMLUnits.parseLength(
  113. _table.getTblGrid().getGridColArray(idx).xgetW()));
  114. }
  115. @Override
  116. public void setColumnWidth(int idx, double width) {
  117. _table.getTblGrid().getGridColArray(idx).setW(Units.toEMU(width));
  118. }
  119. @Override
  120. public double getRowHeight(int row) {
  121. return Units.toPoints(POIXMLUnits.parseLength(_table.getTrArray(row).xgetH()));
  122. }
  123. @Override
  124. public void setRowHeight(int row, double height) {
  125. _table.getTrArray(row).setH(Units.toEMU(height));
  126. }
  127. @Override
  128. public Iterator<XSLFTableRow> iterator(){
  129. return _rows.iterator();
  130. }
  131. public List<XSLFTableRow> getRows(){
  132. return Collections.unmodifiableList(_rows);
  133. }
  134. public XSLFTableRow addRow(){
  135. CTTableRow tr = _table.addNewTr();
  136. XSLFTableRow row = initializeRow(tr);
  137. _rows.add(row);
  138. return row;
  139. }
  140. private XSLFTableRow initializeRow(CTTableRow tr) {
  141. XSLFTableRow row = new XSLFTableRow(tr, this);
  142. // default height is 20 points
  143. row.setHeight(20.0);
  144. for (int i = 0; i < getNumberOfColumns(); i++) {
  145. row.addCell();
  146. }
  147. return row;
  148. }
  149. /**
  150. * Insert a new row at the given index.
  151. * @param rowIdx the row index.
  152. * @since POI 5.0.0
  153. */
  154. public XSLFTableRow insertRow(int rowIdx) {
  155. if (getNumberOfRows() < rowIdx) {
  156. throw new IndexOutOfBoundsException("Cannot insert row at " + rowIdx + "; table has only " + getNumberOfRows() + "rows.");
  157. }
  158. CTTableRow tr = _table.insertNewTr(rowIdx);
  159. XSLFTableRow row = initializeRow(tr);
  160. _rows.add(rowIdx, row);
  161. return row;
  162. }
  163. /**
  164. * Remove the row on the given index
  165. * @param rowIdx the row index
  166. */
  167. public void removeRow(int rowIdx) {
  168. if (getNumberOfRows() < rowIdx) {
  169. throw new IndexOutOfBoundsException("Cannot remove row at " + rowIdx + "; table has only " + getNumberOfRows() + "rows.");
  170. }
  171. _table.removeTr(rowIdx);
  172. _rows.remove(rowIdx);
  173. updateRowColIndexes();
  174. }
  175. /**
  176. * Add a new column at the end of the table.
  177. * @since POI 4.1.2
  178. */
  179. public void addColumn() {
  180. long width = POIXMLUnits.parseLength(_table.getTblGrid().getGridColArray(getNumberOfColumns() - 1).xgetW());
  181. CTTableCol col = _table.getTblGrid().addNewGridCol();
  182. col.setW(width);
  183. for (XSLFTableRow row : _rows) {
  184. XSLFTableCell cell = row.addCell();
  185. new XDDFTextBody(cell, cell.getTextBody(true)).initialize();
  186. }
  187. }
  188. /**
  189. * Insert a new column at the given index.
  190. * @param colIdx the column index.
  191. * @since POI 4.1.2
  192. */
  193. public void insertColumn(int colIdx) {
  194. if (getNumberOfColumns() < colIdx) {
  195. throw new IndexOutOfBoundsException("Cannot insert column at " + colIdx + "; table has only " + getNumberOfColumns() + "columns.");
  196. }
  197. long width = POIXMLUnits.parseLength(_table.getTblGrid().getGridColArray(colIdx).xgetW());
  198. CTTableCol col = _table.getTblGrid().insertNewGridCol(colIdx);
  199. col.setW(width);
  200. for (XSLFTableRow row : _rows) {
  201. XSLFTableCell cell = row.insertCell(colIdx);
  202. new XDDFTextBody(cell, cell.getTextBody(true)).initialize();
  203. }
  204. }
  205. /**
  206. * Remove the column at the given index.
  207. * @param colIdx the column index.
  208. * @since POI 4.1.2
  209. */
  210. public void removeColumn(int colIdx) {
  211. if (getNumberOfColumns() < colIdx) {
  212. throw new IndexOutOfBoundsException("Cannot remove column at " + colIdx + "; table has only " + getNumberOfColumns() + "columns.");
  213. }
  214. _table.getTblGrid().removeGridCol(colIdx);
  215. for (XSLFTableRow row : _rows) {
  216. row.removeCell(colIdx);
  217. }
  218. }
  219. static CTGraphicalObjectFrame prototype(int shapeId){
  220. CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance();
  221. CTGraphicalObjectFrameNonVisual nvGr = frame.addNewNvGraphicFramePr();
  222. CTNonVisualDrawingProps cnv = nvGr.addNewCNvPr();
  223. cnv.setName("Table " + shapeId);
  224. cnv.setId(shapeId);
  225. nvGr.addNewCNvGraphicFramePr().addNewGraphicFrameLocks().setNoGrp(true);
  226. nvGr.addNewNvPr();
  227. frame.addNewXfrm();
  228. CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData();
  229. XmlCursor grCur = gr.newCursor();
  230. grCur.toNextToken();
  231. grCur.beginElement(new QName(XSLFRelation.NS_DRAWINGML, "tbl"));
  232. CTTable tbl = CTTable.Factory.newInstance();
  233. tbl.addNewTblPr();
  234. tbl.addNewTblGrid();
  235. XmlCursor tblCur = tbl.newCursor();
  236. tblCur.moveXmlContents(grCur);
  237. tblCur.dispose();
  238. grCur.dispose();
  239. gr.setUri(TABLE_URI);
  240. return frame;
  241. }
  242. /**
  243. * Merge cells of a table
  244. */
  245. @SuppressWarnings("unused")
  246. public void mergeCells(int firstRow, int lastRow, int firstCol, int lastCol) {
  247. if(firstRow > lastRow) {
  248. throw new IllegalArgumentException(
  249. "Cannot merge, first row > last row : "
  250. + firstRow + " > " + lastRow
  251. );
  252. }
  253. if(firstCol > lastCol) {
  254. throw new IllegalArgumentException(
  255. "Cannot merge, first column > last column : "
  256. + firstCol + " > " + lastCol
  257. );
  258. }
  259. int rowSpan = (lastRow - firstRow) + 1;
  260. boolean mergeRowRequired = rowSpan > 1;
  261. int colSpan = (lastCol - firstCol) + 1;
  262. boolean mergeColumnRequired = colSpan > 1;
  263. for(int i = firstRow; i <= lastRow; i++) {
  264. XSLFTableRow row = _rows.get(i);
  265. for(int colPos = firstCol; colPos <= lastCol; colPos++) {
  266. XSLFTableCell cell = row.getCells().get(colPos);
  267. if(mergeRowRequired) {
  268. if(i == firstRow) {
  269. cell.setRowSpan(rowSpan);
  270. } else {
  271. cell.setVMerge();
  272. }
  273. }
  274. if(mergeColumnRequired) {
  275. if(colPos == firstCol) {
  276. cell.setGridSpan(colSpan);
  277. } else {
  278. cell.setHMerge();
  279. }
  280. }
  281. }
  282. }
  283. }
  284. /**
  285. * Get assigned TableStyle
  286. *
  287. * @return the assigned TableStyle
  288. *
  289. * @since POI 3.15-beta2
  290. */
  291. protected XSLFTableStyle getTableStyle() {
  292. CTTable tab = getCTTable();
  293. // TODO: support inline table style
  294. if (!tab.isSetTblPr() || !tab.getTblPr().isSetTableStyleId()) {
  295. return null;
  296. }
  297. String styleId = tab.getTblPr().getTableStyleId();
  298. XSLFTableStyles styles = getSheet().getSlideShow().getTableStyles();
  299. for (XSLFTableStyle style : styles.getStyles()) {
  300. if (style.getStyleId().equals(styleId)) {
  301. return style;
  302. }
  303. }
  304. return null;
  305. }
  306. /* package */ void updateRowColIndexes() {
  307. int rowIdx = 0;
  308. for (XSLFTableRow xr : this) {
  309. int colIdx = 0;
  310. for (XSLFTableCell tc : xr) {
  311. tc.setRowColIndex(rowIdx, colIdx);
  312. colIdx++;
  313. }
  314. rowIdx++;
  315. }
  316. }
  317. /**
  318. * Calculates the bounding boxes of all cells and updates the dimension of the table
  319. */
  320. public void updateCellAnchor() {
  321. int rows = getNumberOfRows();
  322. int cols = getNumberOfColumns();
  323. double[] colWidths = new double[cols];
  324. double[] rowHeights = new double[rows];
  325. for (int row=0; row<rows; row++) {
  326. rowHeights[row] = getRowHeight(row);
  327. }
  328. for (int col=0; col<cols; col++) {
  329. colWidths[col] = getColumnWidth(col);
  330. }
  331. Rectangle2D tblAnc = getAnchor();
  332. DrawFactory df = DrawFactory.getInstance(null);
  333. double nextY = tblAnc.getY();
  334. double nextX = tblAnc.getX();
  335. // #1 pass - determine row heights, the height values might be too low or 0 ...
  336. for (int row=0; row<rows; row++) {
  337. double maxHeight = 0;
  338. for (int col=0; col<cols; col++) {
  339. XSLFTableCell tc = getCell(row, col);
  340. if (tc == null || tc.getGridSpan() != 1 || tc.getRowSpan() != 1) {
  341. continue;
  342. }
  343. // need to set the anchor before height calculation
  344. tc.setAnchor(new Rectangle2D.Double(0,0,colWidths[col],0));
  345. DrawTextShape dts = df.getDrawable(tc);
  346. maxHeight = Math.max(maxHeight, dts.getTextHeight());
  347. }
  348. rowHeights[row] = Math.max(rowHeights[row],maxHeight);
  349. }
  350. // #2 pass - init properties
  351. for (int row=0; row<rows; row++) {
  352. nextX = tblAnc.getX();
  353. for (int col=0; col<cols; col++) {
  354. Rectangle2D bounds = new Rectangle2D.Double(nextX, nextY, colWidths[col], rowHeights[row]);
  355. XSLFTableCell tc = getCell(row, col);
  356. if (tc != null) {
  357. tc.setAnchor(bounds);
  358. nextX += colWidths[col]+DrawTableShape.borderSize;
  359. }
  360. }
  361. nextY += rowHeights[row]+DrawTableShape.borderSize;
  362. }
  363. // #3 pass - update merge info
  364. for (int row=0; row<rows; row++) {
  365. for (int col=0; col<cols; col++) {
  366. XSLFTableCell tc = getCell(row, col);
  367. if (tc == null) {
  368. continue;
  369. }
  370. Rectangle2D mergedBounds = tc.getAnchor();
  371. for (int col2=col+1; col2<col+tc.getGridSpan(); col2++) {
  372. assert(col2 < cols);
  373. XSLFTableCell tc2 = getCell(row, col2);
  374. assert(tc2.getGridSpan() == 1 && tc2.getRowSpan() == 1);
  375. mergedBounds.add(tc2.getAnchor());
  376. }
  377. for (int row2=row+1; row2<row+tc.getRowSpan(); row2++) {
  378. assert(row2 < rows);
  379. XSLFTableCell tc2 = getCell(row2, col);
  380. assert(tc2.getGridSpan() == 1 && tc2.getRowSpan() == 1);
  381. mergedBounds.add(tc2.getAnchor());
  382. }
  383. tc.setAnchor(mergedBounds);
  384. }
  385. }
  386. setAnchor(new Rectangle2D.Double(tblAnc.getX(),tblAnc.getY(),
  387. nextX-tblAnc.getX(),
  388. nextY-tblAnc.getY()));
  389. }
  390. }