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 34KB

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