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.

XSSFTable.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.usermodel;
  16. import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.OutputStream;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.concurrent.ConcurrentSkipListMap;
  25. import org.apache.poi.ooxml.POIXMLDocumentPart;
  26. import org.apache.poi.openxml4j.opc.PackagePart;
  27. import org.apache.poi.ss.SpreadsheetVersion;
  28. import org.apache.poi.ss.usermodel.DataFormatter;
  29. import org.apache.poi.ss.usermodel.Table;
  30. import org.apache.poi.ss.usermodel.TableStyleInfo;
  31. import org.apache.poi.ss.util.AreaReference;
  32. import org.apache.poi.ss.util.CellReference;
  33. import org.apache.poi.util.Internal;
  34. import org.apache.poi.util.StringUtil;
  35. import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
  36. import org.apache.xmlbeans.XmlException;
  37. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
  38. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
  39. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns;
  40. import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
  41. /**
  42. *
  43. * This class implements the Table Part (Open Office XML Part 4: chapter 3.5.1)
  44. *
  45. * Columns of this table may contains mappings to a subtree of an XML. The root
  46. * element of this subtree can occur multiple times (one for each row of the
  47. * table). The child nodes of the root element can be only attributes or
  48. * elements with maxOccurs=1 property set.
  49. */
  50. public class XSSFTable extends POIXMLDocumentPart implements Table {
  51. private CTTable ctTable;
  52. private transient List<XSSFXmlColumnPr> xmlColumnPrs;
  53. private transient List<XSSFTableColumn> tableColumns;
  54. private transient ConcurrentSkipListMap<String, Integer> columnMap;
  55. private transient CellReference startCellReference;
  56. private transient CellReference endCellReference;
  57. private transient String commonXPath;
  58. private transient String name;
  59. private transient String styleName;
  60. /**
  61. * empty implementation, not attached to a workbook/worksheet yet
  62. */
  63. public XSSFTable() {
  64. super();
  65. ctTable = CTTable.Factory.newInstance();
  66. }
  67. /**
  68. * @param part The part used to initialize the table
  69. * @throws IOException If reading data from the part fails.
  70. * @since POI 3.14-Beta1
  71. */
  72. public XSSFTable(PackagePart part) throws IOException {
  73. super(part);
  74. readFrom(part.getInputStream());
  75. }
  76. /**
  77. * Read table XML from an {@link InputStream}
  78. * @param is The stream which provides the XML data for the table.
  79. * @throws IOException If reading from the stream fails
  80. */
  81. public void readFrom(InputStream is) throws IOException {
  82. try {
  83. TableDocument doc = TableDocument.Factory.parse(is, DEFAULT_XML_OPTIONS);
  84. ctTable = doc.getTable();
  85. } catch (XmlException e) {
  86. throw new IOException(e.getLocalizedMessage());
  87. }
  88. }
  89. /**
  90. * @return owning sheet
  91. */
  92. public XSSFSheet getXSSFSheet(){
  93. return (XSSFSheet) getParent();
  94. }
  95. /**
  96. * write table XML to an {@link OutputStream}
  97. * @param out The stream to write the XML data to
  98. * @throws IOException If writing to the stream fails.
  99. */
  100. public void writeTo(OutputStream out) throws IOException {
  101. updateHeaders();
  102. TableDocument doc = TableDocument.Factory.newInstance();
  103. doc.setTable(ctTable);
  104. doc.save(out, DEFAULT_XML_OPTIONS);
  105. }
  106. @Override
  107. protected void commit() throws IOException {
  108. PackagePart part = getPackagePart();
  109. OutputStream out = part.getOutputStream();
  110. writeTo(out);
  111. out.close();
  112. }
  113. /**
  114. * get the underlying CTTable XML bean
  115. * @return underlying OOXML object
  116. */
  117. @Internal(since="POI 3.15 beta 3")
  118. public CTTable getCTTable() {
  119. return ctTable;
  120. }
  121. /**
  122. * Checks if this Table element contains even a single mapping to the map identified by id
  123. * @param id the XSSFMap ID
  124. * @return true if the Table element contain mappings
  125. */
  126. public boolean mapsTo(long id){
  127. List<XSSFXmlColumnPr> pointers = getXmlColumnPrs();
  128. for (XSSFXmlColumnPr pointer: pointers) {
  129. if (pointer.getMapId()==id) {
  130. return true;
  131. }
  132. }
  133. return false;
  134. }
  135. /**
  136. *
  137. * Calculates the xpath of the root element for the table. This will be the common part
  138. * of all the mapping's xpaths
  139. * Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called.
  140. *
  141. * @return the xpath of the table's root element
  142. */
  143. public String getCommonXpath() {
  144. if (commonXPath == null) {
  145. String[] commonTokens = {};
  146. for (XSSFTableColumn column : getColumns()) {
  147. if (column.getXmlColumnPr()!=null) {
  148. String xpath = column.getXmlColumnPr().getXPath();
  149. String[] tokens = xpath.split("/");
  150. if (commonTokens.length==0) {
  151. commonTokens = tokens;
  152. } else {
  153. final int maxLength = Math.min(commonTokens.length, tokens.length);
  154. for (int i =0; i<maxLength; i++) {
  155. if (!commonTokens[i].equals(tokens[i])) {
  156. List<String> subCommonTokens = Arrays.asList(commonTokens).subList(0, i);
  157. String[] container = {};
  158. commonTokens = subCommonTokens.toArray(container);
  159. break;
  160. }
  161. }
  162. }
  163. }
  164. }
  165. commonTokens[0] = "";
  166. commonXPath = StringUtil.join(commonTokens, "/");
  167. }
  168. return commonXPath;
  169. }
  170. /**
  171. * Note this list is static - once read, it does not notice later changes to the underlying column structures
  172. * To clear the cache, call {@link #updateHeaders}
  173. * @return List of XSSFTableColumn
  174. * @since 4.0.0
  175. */
  176. public List<XSSFTableColumn> getColumns() {
  177. if (tableColumns == null) {
  178. List<XSSFTableColumn> columns = new ArrayList<>();
  179. CTTableColumns ctTableColumns = ctTable.getTableColumns();
  180. if (ctTableColumns != null) {
  181. for (CTTableColumn column : ctTableColumns.getTableColumnList()) {
  182. XSSFTableColumn tableColumn = new XSSFTableColumn(this, column);
  183. columns.add(tableColumn);
  184. }
  185. }
  186. tableColumns = Collections.unmodifiableList(columns);
  187. }
  188. return tableColumns;
  189. }
  190. /**
  191. * Use {@link XSSFTableColumn#getXmlColumnPr()} instead.
  192. */
  193. private List<XSSFXmlColumnPr> getXmlColumnPrs() {
  194. if (xmlColumnPrs == null) {
  195. xmlColumnPrs = new ArrayList<>();
  196. for (XSSFTableColumn column: getColumns()) {
  197. XSSFXmlColumnPr xmlColumnPr = column.getXmlColumnPr();
  198. if (xmlColumnPr != null) {
  199. xmlColumnPrs.add(xmlColumnPr);
  200. }
  201. }
  202. }
  203. return xmlColumnPrs;
  204. }
  205. /**
  206. * Add a new column to the right end of the table.
  207. *
  208. * @param columnName
  209. * the unique name of the column, must not be {@code null}
  210. * @return the created table column
  211. * @since 4.0.0
  212. */
  213. public XSSFTableColumn createColumn(String columnName) {
  214. return createColumn(columnName, getColumnCount());
  215. }
  216. /**
  217. * Adds a new column to the table.
  218. *
  219. * @param columnName
  220. * the unique name of the column, or {@code null} for a generated name
  221. * @param columnIndex
  222. * the 0-based position of the column in the table
  223. * @return the created table column
  224. * @throws IllegalArgumentException
  225. * if the column name is not unique or missing or if the column
  226. * can't be created at the given index
  227. * @since 4.0.0
  228. */
  229. public XSSFTableColumn createColumn(String columnName, int columnIndex) {
  230. int columnCount = getColumnCount();
  231. if(columnIndex < 0 || columnIndex > columnCount) {
  232. throw new IllegalArgumentException("Column index out of bounds");
  233. }
  234. // Ensure we have Table Columns
  235. CTTableColumns columns = ctTable.getTableColumns();
  236. if (columns == null) {
  237. columns = ctTable.addNewTableColumns();
  238. }
  239. // check if name is unique and calculate unique column id
  240. long nextColumnId = 0;
  241. for (XSSFTableColumn tableColumn : getColumns()) {
  242. if (columnName != null && columnName.equalsIgnoreCase(tableColumn.getName())) {
  243. throw new IllegalArgumentException("Column '" + columnName
  244. + "' already exists. Column names must be unique per table.");
  245. }
  246. nextColumnId = Math.max(nextColumnId, tableColumn.getId());
  247. }
  248. // Bug #62740, the logic was just re-using the existing max ID, not incrementing beyond it.
  249. nextColumnId++;
  250. // Add the new Column
  251. CTTableColumn column = columns.insertNewTableColumn(columnIndex);
  252. columns.setCount(columns.sizeOfTableColumnArray());
  253. column.setId(nextColumnId);
  254. if(columnName != null) {
  255. column.setName(columnName);
  256. } else {
  257. column.setName("Column " + nextColumnId);
  258. }
  259. if (ctTable.getRef() != null) {
  260. // calculate new area
  261. int newColumnCount = columnCount + 1;
  262. CellReference tableStart = getStartCellReference();
  263. CellReference tableEnd = getEndCellReference();
  264. SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
  265. CellReference newTableEnd = new CellReference(tableEnd.getRow(),
  266. tableStart.getCol() + newColumnCount - 1);
  267. AreaReference newTableArea = new AreaReference(tableStart, newTableEnd, version);
  268. setCellRef(newTableArea);
  269. }
  270. updateHeaders();
  271. return getColumns().get(columnIndex);
  272. }
  273. /**
  274. * Remove a column from the table.
  275. *
  276. * @param column
  277. * the column to remove
  278. * @since 4.0.0
  279. */
  280. public void removeColumn(XSSFTableColumn column) {
  281. int columnIndex = getColumns().indexOf(column);
  282. if (columnIndex >= 0) {
  283. ctTable.getTableColumns().removeTableColumn(columnIndex);
  284. updateReferences();
  285. updateHeaders();
  286. }
  287. }
  288. /**
  289. * Remove a column from the table.
  290. *
  291. * @param columnIndex
  292. * the 0-based position of the column in the table
  293. * @throws IllegalArgumentException
  294. * if no column at the index exists or if the table has only a
  295. * single column
  296. * @since 4.0.0
  297. */
  298. public void removeColumn(int columnIndex) {
  299. if (columnIndex < 0 || columnIndex > getColumnCount() - 1) {
  300. throw new IllegalArgumentException("Column index out of bounds");
  301. }
  302. if(getColumnCount() == 1) {
  303. throw new IllegalArgumentException("Table must have at least one column");
  304. }
  305. CTTableColumns tableColumns = ctTable.getTableColumns();
  306. tableColumns.removeTableColumn(columnIndex);
  307. tableColumns.setCount(tableColumns.getTableColumnList().size());
  308. updateReferences();
  309. updateHeaders();
  310. }
  311. /**
  312. * @return the name of the Table, if set
  313. */
  314. @Override
  315. public String getName() {
  316. if (name == null && ctTable.getName() != null) {
  317. setName(ctTable.getName());
  318. }
  319. return name;
  320. }
  321. /**
  322. * Changes the name of the Table
  323. * @param newName The name of the table.
  324. */
  325. public void setName(String newName) {
  326. if (newName == null) {
  327. ctTable.unsetName();
  328. name = null;
  329. return;
  330. }
  331. ctTable.setName(newName);
  332. name = newName;
  333. }
  334. /**
  335. * @return the table style name, if set
  336. * @since 3.17 beta 1
  337. */
  338. @Override
  339. public String getStyleName() {
  340. if (styleName == null && ctTable.isSetTableStyleInfo()) {
  341. setStyleName(ctTable.getTableStyleInfo().getName());
  342. }
  343. return styleName;
  344. }
  345. /**
  346. * Changes the name of the Table
  347. * @param newStyleName The name of the style.
  348. * @since 3.17 beta 1
  349. */
  350. public void setStyleName(String newStyleName) {
  351. if (newStyleName == null) {
  352. if (ctTable.isSetTableStyleInfo()) {
  353. ctTable.getTableStyleInfo().unsetName();
  354. }
  355. styleName = null;
  356. return;
  357. }
  358. if (! ctTable.isSetTableStyleInfo()) {
  359. ctTable.addNewTableStyleInfo();
  360. }
  361. ctTable.getTableStyleInfo().setName(newStyleName);
  362. styleName = newStyleName;
  363. }
  364. /**
  365. * @return the display name of the Table, if set
  366. */
  367. public String getDisplayName() {
  368. return ctTable.getDisplayName();
  369. }
  370. /**
  371. * Changes the display name of the Table
  372. * @param name to use
  373. */
  374. public void setDisplayName(String name) {
  375. if (name == null || name.isEmpty()) {
  376. throw new IllegalArgumentException("Display name must not be null or empty");
  377. }
  378. ctTable.setDisplayName(name);
  379. }
  380. /**
  381. * Get the area reference for the cells which this table covers. The area
  382. * includes header rows and totals rows.
  383. *
  384. * Does not track updates to underlying changes to CTTable To synchronize
  385. * with changes to the underlying CTTable, call {@link #updateReferences()}.
  386. *
  387. * @return the area of the table
  388. * @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref"
  389. * @since 3.17 beta 1
  390. */
  391. public AreaReference getCellReferences() {
  392. return new AreaReference(
  393. getStartCellReference(),
  394. getEndCellReference(),
  395. SpreadsheetVersion.EXCEL2007
  396. );
  397. }
  398. /**
  399. * Set the area reference for the cells which this table covers. The area
  400. * includes includes header rows and totals rows. Automatically synchronizes
  401. * any changes by calling {@link #updateHeaders()}.
  402. *
  403. * Note: The area's width should be identical to the amount of columns in
  404. * the table or the table may be invalid. All header rows, totals rows and
  405. * at least one data row must fit inside the area. Updating the area with
  406. * this method does not create or remove any columns and does not change any
  407. * cell values.
  408. *
  409. * @see "Open Office XML Part 4: chapter 3.5.1.2, attribute ref"
  410. * @since 3.17 beta 1
  411. */
  412. public void setCellReferences(AreaReference refs) {
  413. setCellRef(refs);
  414. }
  415. @Internal
  416. protected void setCellRef(AreaReference refs) {
  417. // Strip the sheet name,
  418. // CTWorksheet.getTableParts defines in which sheet the table is
  419. String ref = refs.formatAsString();
  420. if (ref.indexOf('!') != -1) {
  421. ref = ref.substring(ref.indexOf('!')+1);
  422. }
  423. // Update
  424. ctTable.setRef(ref);
  425. if (ctTable.isSetAutoFilter()) {
  426. String filterRef;
  427. int totalsRowCount = getTotalsRowCount();
  428. if (totalsRowCount == 0) {
  429. filterRef = ref;
  430. } else {
  431. final CellReference start = new CellReference(refs.getFirstCell().getRow(), refs.getFirstCell().getCol());
  432. // account for footer row(s) in auto-filter range, which doesn't include footers
  433. final CellReference end = new CellReference(refs.getLastCell().getRow() - totalsRowCount, refs.getLastCell().getCol());
  434. // this won't have sheet references because we built the cell references without them
  435. filterRef = new AreaReference(start, end, SpreadsheetVersion.EXCEL2007).formatAsString();
  436. }
  437. ctTable.getAutoFilter().setRef(filterRef);
  438. }
  439. // Have everything recomputed
  440. updateReferences();
  441. updateHeaders();
  442. }
  443. /**
  444. * Set the area reference for the cells which this table covers. The area
  445. * includes includes header rows and totals rows.
  446. *
  447. * Updating the area with this method will create new column as necessary to
  448. * the right side of the table but will not modify any cell values.
  449. *
  450. * @param tableArea
  451. * the new area of the table
  452. * @throws IllegalArgumentException
  453. * if the area is {@code null} or not
  454. * @since 4.0.0
  455. */
  456. public void setArea(AreaReference tableArea) {
  457. if (tableArea == null) {
  458. throw new IllegalArgumentException("AreaReference must not be null");
  459. }
  460. String areaSheetName = tableArea.getFirstCell().getSheetName();
  461. if (areaSheetName != null && !areaSheetName.equals(getXSSFSheet().getSheetName())) {
  462. // TODO to move a table from one sheet to another
  463. // CTWorksheet.getTableParts needs to be updated on both sheets
  464. throw new IllegalArgumentException(
  465. "The AreaReference must not reference a different sheet");
  466. }
  467. int rowCount = (tableArea.getLastCell().getRow() - tableArea.getFirstCell().getRow()) + 1;
  468. int minimumRowCount = 1 + getHeaderRowCount() + getTotalsRowCount();
  469. if (rowCount < minimumRowCount) {
  470. throw new IllegalArgumentException("AreaReference needs at least " + minimumRowCount
  471. + " rows, to cover at least one data row and all header rows and totals rows");
  472. }
  473. // Strip the sheet name,
  474. // CTWorksheet.getTableParts defines in which sheet the table is
  475. String ref = tableArea.formatAsString();
  476. if (ref.indexOf('!') != -1) {
  477. ref = ref.substring(ref.indexOf('!') + 1);
  478. }
  479. // Update
  480. ctTable.setRef(ref);
  481. if (ctTable.isSetAutoFilter()) {
  482. ctTable.getAutoFilter().setRef(ref);
  483. }
  484. updateReferences();
  485. // add or remove columns on the right side of the table
  486. int columnCount = getColumnCount();
  487. int newColumnCount = (tableArea.getLastCell().getCol() - tableArea.getFirstCell().getCol()) + 1;
  488. if (newColumnCount > columnCount) {
  489. for (int i = columnCount; i < newColumnCount; i++) {
  490. createColumn(null, i);
  491. }
  492. } else if (newColumnCount < columnCount) {
  493. for (int i = columnCount; i > newColumnCount; i--) {
  494. removeColumn(i -1);
  495. }
  496. }
  497. updateHeaders();
  498. }
  499. /**
  500. * Get the area that this table covers.
  501. *
  502. * @return the table's area or {@code null} if the area has not been
  503. * initialized
  504. * @since 4.0.0
  505. */
  506. public AreaReference getArea() {
  507. String ref = ctTable.getRef();
  508. if (ref != null) {
  509. SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
  510. return new AreaReference(ctTable.getRef(), version);
  511. } else {
  512. return null;
  513. }
  514. }
  515. /**
  516. * @return The reference for the cell in the top-left part of the table
  517. * (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
  518. *
  519. * Does not track updates to underlying changes to CTTable
  520. * To synchronize with changes to the underlying CTTable,
  521. * call {@link #updateReferences()}.
  522. */
  523. public CellReference getStartCellReference() {
  524. if (startCellReference==null) {
  525. setCellReferences();
  526. }
  527. return startCellReference;
  528. }
  529. /**
  530. * @return The reference for the cell in the bottom-right part of the table
  531. * (see Open Office XML Part 4: chapter 3.5.1.2, attribute ref)
  532. *
  533. * Does not track updates to underlying changes to CTTable
  534. * To synchronize with changes to the underlying CTTable,
  535. * call {@link #updateReferences()}.
  536. */
  537. public CellReference getEndCellReference() {
  538. if (endCellReference==null) {
  539. setCellReferences();
  540. }
  541. return endCellReference;
  542. }
  543. /**
  544. * @since POI 3.15 beta 3
  545. */
  546. private void setCellReferences() {
  547. String ref = ctTable.getRef();
  548. if (ref != null) {
  549. String[] boundaries = ref.split(":", 2);
  550. String from = boundaries[0];
  551. String to = boundaries.length == 2 ? boundaries[1] : boundaries[0];
  552. startCellReference = new CellReference(from);
  553. endCellReference = new CellReference(to);
  554. }
  555. }
  556. /**
  557. * Clears the cached values set by {@link #getStartCellReference()}
  558. * and {@link #getEndCellReference()}.
  559. * The next call to {@link #getStartCellReference()} and
  560. * {@link #getEndCellReference()} will synchronize the
  561. * cell references with the underlying {@code CTTable}.
  562. * Thus this method is inexpensive.
  563. *
  564. * @since POI 3.15 beta 3
  565. */
  566. public void updateReferences() {
  567. startCellReference = null;
  568. endCellReference = null;
  569. }
  570. /**
  571. * Get the total number of rows in this table, including all
  572. * {@linkplain #getHeaderRowCount() header rows} and all
  573. * {@linkplain #getTotalsRowCount() totals rows}. (Note: in this version
  574. * autofiltering is ignored)
  575. *
  576. * Returns {@code 0} if the start or end cell references are not set.
  577. *
  578. * Does not track updates to underlying changes to CTTable To synchronize
  579. * with changes to the underlying CTTable, call {@link #updateReferences()}.
  580. *
  581. * @return the total number of rows
  582. */
  583. public int getRowCount() {
  584. CellReference from = getStartCellReference();
  585. CellReference to = getEndCellReference();
  586. int rowCount = 0;
  587. if (from!=null && to!=null) {
  588. rowCount = to.getRow() - from.getRow() + 1;
  589. }
  590. return rowCount;
  591. }
  592. /**
  593. * Get the number of data rows in this table. This does not include any
  594. * header rows or totals rows.
  595. *
  596. * Returns {@code 0} if the start or end cell references are not set.
  597. *
  598. * Does not track updates to underlying changes to CTTable To synchronize
  599. * with changes to the underlying CTTable, call {@link #updateReferences()}.
  600. *
  601. * @return the number of data rows
  602. * @since 4.0.0
  603. */
  604. public int getDataRowCount() {
  605. CellReference from = getStartCellReference();
  606. CellReference to = getEndCellReference();
  607. int rowCount = 0;
  608. if (from != null && to != null) {
  609. rowCount = (to.getRow() - from.getRow() + 1) - getHeaderRowCount()
  610. - getTotalsRowCount();
  611. }
  612. return rowCount;
  613. }
  614. /**
  615. * Set the number of rows in the data area of the table. This does not
  616. * affect any header rows or totals rows.
  617. *
  618. * If the new row count is less than the current row count, superfluous rows
  619. * will be cleared. If the new row count is greater than the current row
  620. * count, cells below the table will be overwritten by the table.
  621. *
  622. * To resize the table without overwriting cells, use
  623. * {@link #setArea(AreaReference)} instead.
  624. *
  625. * @param newDataRowCount
  626. * new row count for the table
  627. * @throws IllegalArgumentException
  628. * if the row count is less than 1
  629. * @since 4.0.0
  630. */
  631. public void setDataRowCount(int newDataRowCount) {
  632. if (newDataRowCount < 1) {
  633. throw new IllegalArgumentException("Table must have at least one data row");
  634. }
  635. updateReferences();
  636. int dataRowCount = getDataRowCount();
  637. if (dataRowCount == newDataRowCount) {
  638. return;
  639. }
  640. CellReference tableStart = getStartCellReference();
  641. CellReference tableEnd = getEndCellReference();
  642. SpreadsheetVersion version = getXSSFSheet().getWorkbook().getSpreadsheetVersion();
  643. // calculate new area
  644. int newTotalRowCount = getHeaderRowCount() + newDataRowCount + getTotalsRowCount();
  645. CellReference newTableEnd = new CellReference(tableStart.getRow() + newTotalRowCount - 1,
  646. tableEnd.getCol());
  647. AreaReference newTableArea = new AreaReference(tableStart, newTableEnd, version);
  648. // clear cells
  649. CellReference clearAreaStart;
  650. CellReference clearAreaEnd;
  651. if (newDataRowCount < dataRowCount) {
  652. // table size reduced -
  653. // clear all table cells that are outside of the new area
  654. clearAreaStart = new CellReference(newTableArea.getLastCell().getRow() + 1,
  655. newTableArea.getFirstCell().getCol());
  656. clearAreaEnd = tableEnd;
  657. } else {
  658. // table size increased -
  659. // clear all cells below the table that are inside the new area
  660. clearAreaStart = new CellReference(tableEnd.getRow() + 1,
  661. newTableArea.getFirstCell().getCol());
  662. clearAreaEnd = newTableEnd;
  663. }
  664. AreaReference areaToClear = new AreaReference(clearAreaStart, clearAreaEnd, version);
  665. for (CellReference cellRef : areaToClear.getAllReferencedCells()) {
  666. XSSFRow row = getXSSFSheet().getRow(cellRef.getRow());
  667. if (row != null) {
  668. XSSFCell cell = row.getCell(cellRef.getCol());
  669. if (cell != null) {
  670. cell.setBlank();
  671. cell.setCellStyle(null);
  672. }
  673. }
  674. }
  675. // update table area
  676. setCellRef(newTableArea);
  677. }
  678. /**
  679. * Get the total number of columns in this table.
  680. *
  681. * @return the column count
  682. * @since 4.0.0
  683. */
  684. public int getColumnCount() {
  685. CTTableColumns tableColumns = ctTable.getTableColumns();
  686. if(tableColumns == null) {
  687. return 0;
  688. }
  689. // Casting to int should be safe here - tables larger than the
  690. // sheet (which holds the actual data of the table) can't exists.
  691. return (int) tableColumns.getCount();
  692. }
  693. /**
  694. * Synchronize table headers with cell values in the parent sheet.
  695. * Headers <em>must</em> be in sync, otherwise Excel will display a
  696. * "Found unreadable content" message on startup.
  697. *
  698. * If calling both {@link #updateReferences()} and
  699. * this method, {@link #updateReferences()}
  700. * should be called first.
  701. *
  702. * Note that a Table <em>must</em> have a header. To reproduce
  703. * the equivalent of inserting a table in Excel without Headers,
  704. * manually add cells with values of "Column1", "Column2" etc first.
  705. */
  706. public void updateHeaders() {
  707. XSSFSheet sheet = (XSSFSheet)getParent();
  708. CellReference ref = getStartCellReference();
  709. if (ref == null) return;
  710. int headerRow = ref.getRow();
  711. int firstHeaderColumn = ref.getCol();
  712. XSSFRow row = sheet.getRow(headerRow);
  713. DataFormatter formatter = new DataFormatter();
  714. if (row != null && row.getCTRow().validate()) {
  715. int cellnum = firstHeaderColumn;
  716. CTTableColumns ctTableColumns = getCTTable().getTableColumns();
  717. if(ctTableColumns != null) {
  718. for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
  719. XSSFCell cell = row.getCell(cellnum);
  720. if (cell != null) {
  721. col.setName(formatter.formatCellValue(cell));
  722. }
  723. cellnum++;
  724. }
  725. }
  726. }
  727. tableColumns = null;
  728. columnMap = null;
  729. xmlColumnPrs = null;
  730. commonXPath = null;
  731. }
  732. /**
  733. * Gets the relative column index of a column in this table having the header name {@code column}.
  734. * The column index is relative to the left-most column in the table, 0-indexed.
  735. * Returns {@code -1} if {@code column} is not a header name in table.
  736. *
  737. * Column Header names are case-insensitive
  738. *
  739. * Note: this function caches column names for performance. To flush the cache (because columns
  740. * have been moved or column headers have been changed), {@link #updateHeaders()} must be called.
  741. *
  742. * @since 3.15 beta 2
  743. */
  744. @Override
  745. public int findColumnIndex(String columnHeader) {
  746. if (columnHeader == null) return -1;
  747. if (columnMap == null) {
  748. columnMap = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
  749. int i = 0;
  750. for (XSSFTableColumn column : getColumns()) {
  751. String columnName = column.getName();
  752. columnMap.put(columnName, i);
  753. i++;
  754. }
  755. }
  756. // Table column names with special characters need a single quote escape
  757. // but the escape is not present in the column definition
  758. String unescapedString = columnHeader
  759. .replace("''", "'")
  760. .replace("'#", "#");
  761. Integer idx = columnMap.get(unescapedString);
  762. return idx == null ? -1 : idx;
  763. }
  764. /**
  765. * @since 3.15 beta 2
  766. */
  767. @Override
  768. public String getSheetName() {
  769. return getXSSFSheet().getSheetName();
  770. }
  771. /**
  772. * Note: This is misleading. The Spec indicates this is true if the totals row
  773. * has <b><i>ever</i></b> been shown, not whether or not it is currently displayed.
  774. * Use {@link #getTotalsRowCount()} &gt; 0 to decide whether or not the totals row is visible.
  775. * @since 3.15 beta 2
  776. * @see #getTotalsRowCount()
  777. */
  778. @Override
  779. public boolean isHasTotalsRow() {
  780. return ctTable.getTotalsRowShown();
  781. }
  782. /**
  783. * @return 0 for no totals rows, 1 for totals row shown.
  784. * Values &gt; 1 are not currently used by Excel up through 2016, and the OOXML spec
  785. * doesn't define how they would be implemented.
  786. * @since 3.17 beta 1
  787. */
  788. @Override
  789. public int getTotalsRowCount() {
  790. return (int) ctTable.getTotalsRowCount();
  791. }
  792. /**
  793. * @return 0 for no header rows, 1 for table headers shown.
  794. * Values &gt; 1 might be used by Excel for pivot tables?
  795. * @since 3.17 beta 1
  796. */
  797. @Override
  798. public int getHeaderRowCount() {
  799. return (int) ctTable.getHeaderRowCount();
  800. }
  801. /**
  802. * @since 3.15 beta 2
  803. */
  804. @Override
  805. public int getStartColIndex() {
  806. return getStartCellReference().getCol();
  807. }
  808. /**
  809. * @since 3.15 beta 2
  810. */
  811. @Override
  812. public int getStartRowIndex() {
  813. return getStartCellReference().getRow();
  814. }
  815. /**
  816. * @since 3.15 beta 2
  817. */
  818. @Override
  819. public int getEndColIndex() {
  820. return getEndCellReference().getCol();
  821. }
  822. /**
  823. * @since 3.15 beta 2
  824. */
  825. @Override
  826. public int getEndRowIndex() {
  827. return getEndCellReference().getRow();
  828. }
  829. /**
  830. * @since 3.17 beta 1
  831. */
  832. @Override
  833. public TableStyleInfo getStyle() {
  834. if (! ctTable.isSetTableStyleInfo()) return null;
  835. return new XSSFTableStyleInfo(((XSSFSheet) getParent()).getWorkbook().getStylesSource(), ctTable.getTableStyleInfo());
  836. }
  837. /**
  838. * @see org.apache.poi.ss.usermodel.Table#contains(org.apache.poi.ss.usermodel.Cell)
  839. * @since 3.17 beta 1
  840. */
  841. @Override
  842. public boolean contains(CellReference cell) {
  843. if (cell == null) return false;
  844. // check if cell is on the same sheet as the table
  845. if ( ! getSheetName().equals(cell.getSheetName())) return false;
  846. // check if the cell is inside the table
  847. return cell.getRow() >= getStartRowIndex()
  848. && cell.getRow() <= getEndRowIndex()
  849. && cell.getCol() >= getStartColIndex()
  850. && cell.getCol() <= getEndColIndex();
  851. }
  852. /**
  853. * Remove relations
  854. */
  855. protected void onTableDelete() {
  856. for (RelationPart part : getRelationParts()) {
  857. removeRelation(part.getDocumentPart(), true);
  858. }
  859. }
  860. }