12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238 |
- /* ====================================================================
- 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.xssf.usermodel;
-
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.time.LocalDateTime;
- import java.util.Calendar;
- import java.util.Date;
-
- import org.apache.poi.ss.SpreadsheetVersion;
- import org.apache.poi.ss.formula.FormulaParser;
- import org.apache.poi.ss.formula.FormulaRenderer;
- import org.apache.poi.ss.formula.FormulaType;
- import org.apache.poi.ss.formula.SharedFormula;
- import org.apache.poi.ss.formula.eval.ErrorEval;
- import org.apache.poi.ss.formula.ptg.ErrPtg;
- import org.apache.poi.ss.formula.ptg.Ptg;
- import org.apache.poi.ss.usermodel.Cell;
- import org.apache.poi.ss.usermodel.CellBase;
- import org.apache.poi.ss.usermodel.CellCopyContext;
- import org.apache.poi.ss.usermodel.CellCopyPolicy;
- import org.apache.poi.ss.usermodel.CellStyle;
- import org.apache.poi.ss.usermodel.CellType;
- import org.apache.poi.ss.usermodel.Comment;
- import org.apache.poi.ss.usermodel.DataFormatter;
- import org.apache.poi.ss.usermodel.DateUtil;
- import org.apache.poi.ss.usermodel.FormulaError;
- import org.apache.poi.ss.usermodel.FormulaEvaluator;
- import org.apache.poi.ss.usermodel.Hyperlink;
- import org.apache.poi.ss.usermodel.RichTextString;
- import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
- import org.apache.poi.ss.util.CellAddress;
- import org.apache.poi.ss.util.CellRangeAddress;
- import org.apache.poi.ss.util.CellReference;
- import org.apache.poi.ss.util.CellUtil;
- import org.apache.poi.util.Beta;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.LocaleUtil;
- import org.apache.poi.xssf.model.CalculationChain;
- import org.apache.poi.xssf.model.SharedStringsTable;
- import org.apache.poi.xssf.model.StylesTable;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellFormulaType;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType;
-
- /**
- * High level representation of a cell in a row of a spreadsheet.
- * <p>
- * Cells can be numeric, formula-based or string-based (text). The cell type
- * specifies this. String cells cannot contain numbers and numeric cells cannot
- * contain strings (at least according to our model). Client apps should do the
- * conversions themselves. Formula cells have the formula string, as well as
- * the formula result, which can be numeric or string.
- * </p>
- * <p>
- * Cells should have their number (0 based) before being added to a row. Only
- * cells that have values should be added.
- * </p>
- */
- public final class XSSFCell extends CellBase {
-
- private static final String FALSE_AS_STRING = "0";
- private static final String TRUE_AS_STRING = "1";
- private static final String FALSE = "FALSE";
- private static final String TRUE = "TRUE";
-
- /**
- * the xml bean containing information about the cell's location, value,
- * data type, formatting, and formula
- */
- private CTCell _cell;
-
- /**
- * the XSSFRow this cell belongs to
- */
- private final XSSFRow _row;
-
- /**
- * 0-based column index
- */
- private int _cellNum;
-
- /**
- * Table of strings shared across this workbook.
- * If two cells contain the same string, then the cell value is the same index into SharedStringsTable
- */
- private final SharedStringsTable _sharedStringSource;
-
- /**
- * Table of cell styles shared across all cells in a workbook.
- */
- private final StylesTable _stylesSource;
-
- /**
- * Construct a XSSFCell.
- *
- * @param row the parent row.
- * @param cell the xml bean containing information about the cell.
- */
- protected XSSFCell(XSSFRow row, CTCell cell) {
- _cell = cell;
- _row = row;
- String rval = cell.getR();
- if (rval != null) {
- _cellNum = new CellReference(rval).getCol();
- } else {
- int prevNum = row.getLastCellNum();
- if(prevNum != -1){
- _cellNum = row.getCell(prevNum-1, MissingCellPolicy.RETURN_NULL_AND_BLANK).getColumnIndex() + 1;
- }
- }
- _sharedStringSource = row.getSheet().getWorkbook().getSharedStringSource();
- _stylesSource = row.getSheet().getWorkbook().getStylesSource();
- }
-
- @Override
- protected SpreadsheetVersion getSpreadsheetVersion() {
- return SpreadsheetVersion.EXCEL2007;
- }
-
- /**
- * Copy cell value, formula and style, from srcCell per cell copy policy
- * If srcCell is null, clears the cell value and cell style per cell copy policy
- *
- * This does not shift references in formulas. Use {@link org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter} to shift references in formulas.
- *
- * @param srcCell The cell to take value, formula and style from
- * @param policy The policy for copying the information, see {@link CellCopyPolicy}
- * @throws IllegalArgumentException if copy cell style and srcCell is from a different workbook
- * @see CellUtil#copyCell(Cell, Cell, CellCopyPolicy, CellCopyContext)
- */
- @Beta
- public void copyCellFrom(Cell srcCell, CellCopyPolicy policy) {
- CellUtil.copyCell(srcCell, this, policy, null);
- }
-
- /**
- * @return table of strings shared across this workbook
- */
- protected SharedStringsTable getSharedStringSource() {
- return _sharedStringSource;
- }
-
- /**
- * @return table of cell styles shared across this workbook
- */
- protected StylesTable getStylesSource() {
- return _stylesSource;
- }
-
- /**
- * Returns the sheet this cell belongs to
- *
- * @return the sheet this cell belongs to
- */
- @Override
- public XSSFSheet getSheet() {
- return getRow().getSheet();
- }
-
- /**
- * Returns the row this cell belongs to
- *
- * @return the row this cell belongs to
- */
- @Override
- public XSSFRow getRow() {
- return _row;
- }
-
- /**
- * Get the value of the cell as a boolean.
- * <p>
- * For strings, numbers, and errors, we throw an exception. For blank cells we return a false.
- * </p>
- * @return the value of the cell as a boolean
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()}
- * is not {@link CellType#BOOLEAN}, {@link CellType#BLANK} or {@link CellType#FORMULA}
- */
- @Override
- public boolean getBooleanCellValue() {
- CellType cellType = getCellType();
- switch(cellType) {
- case BLANK:
- return false;
- case BOOLEAN:
- return _cell.isSetV() && TRUE_AS_STRING.equals(_cell.getV());
- case FORMULA:
- //YK: should throw an exception if requesting boolean value from a non-boolean formula
- return _cell.isSetV() && TRUE_AS_STRING.equals(_cell.getV());
- default:
- throw typeMismatch(CellType.BOOLEAN, cellType, false);
- }
- }
-
- /**
- * Set a boolean value for the cell
- *
- * @param value the boolean value to set this cell to. For formulas, we'll set the
- * precalculated value, for booleans we'll set its value. For other types, we
- * will change the cell to a boolean cell and set its value.
- */
- @Override
- public void setCellValue(boolean value) {
- _cell.setT(STCellType.B);
- _cell.setV(value ? TRUE_AS_STRING : FALSE_AS_STRING);
- }
-
- /**
- * Get the value of the cell as a number.
- * <p>
- * For strings we throw an exception. For blank cells we return a 0.
- * For formulas or error cells we return the precalculated value;
- * </p>
- * @return the value of the cell as a number
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is {@link CellType#STRING}
- * @throws NumberFormatException if the cell value isn't a parsable {@code double}.
- * @see DataFormatter for turning this number into a string similar to that which Excel would render this number as.
- */
- @Override
- public double getNumericCellValue() {
- CellType valueType = isFormulaCell() ? getCachedFormulaResultType() : getCellType();
- switch(valueType) {
- case BLANK:
- return 0.0;
- case NUMERIC:
- if(_cell.isSetV()) {
- String v = _cell.getV();
- if (v.isEmpty()) {
- return 0.0;
- }
- try {
- return Double.parseDouble(v);
- } catch(NumberFormatException e) {
- throw typeMismatch(CellType.NUMERIC, CellType.STRING, false);
- }
- } else {
- return 0.0;
- }
- case FORMULA:
- throw new AssertionError();
- default:
- throw typeMismatch(CellType.NUMERIC, valueType, false);
- }
- }
-
- @Override
- public void setCellValueImpl(double value) {
- _cell.setT(STCellType.N);
- _cell.setV(String.valueOf(value));
- }
-
- /**
- * Get the value of the cell as a string
- * <p>
- * For numeric cells we throw an exception. For blank cells we return an empty string.
- * For formulaCells that are not string Formulas, we throw an exception
- * </p>
- * @return the value of the cell as a string
- */
- @Override
- public String getStringCellValue() {
- return getRichStringCellValue().getString();
- }
-
- /**
- * Get the value of the cell as a XSSFRichTextString
- * <p>
- * For numeric cells we throw an exception. For blank cells we return an empty string.
- * For formula cells we return the pre-calculated value if a string, otherwise an exception
- * </p>
- * @return the value of the cell as a XSSFRichTextString
- */
- @Override
- public XSSFRichTextString getRichStringCellValue() {
- CellType cellType = getCellType();
- XSSFRichTextString rt;
- switch (cellType) {
- case BLANK:
- rt = new XSSFRichTextString("");
- break;
- case STRING:
- rt = findStringValue();
- break;
- case FORMULA: {
- CellType cachedValueType = getBaseCellType(false);
- if (cachedValueType != CellType.STRING) {
- throw typeMismatch(CellType.STRING, cachedValueType, true);
- }
- rt = findStringValue();
- break;
- }
- default:
- throw typeMismatch(CellType.STRING, cellType, false);
- }
- rt.setStylesTableReference(_stylesSource);
- return rt;
- }
-
- private XSSFRichTextString findStringValue() {
- XSSFRichTextString rt;
- STCellType.Enum xmlbeanCellType = _cell.getT();
- if (xmlbeanCellType == STCellType.INLINE_STR) {
- if(_cell.isSetIs()) {
- //string is expressed directly in the cell definition instead of implementing the shared string table.
- rt = new XSSFRichTextString(_cell.getIs());
- } else if (_cell.isSetV()) {
- //cached result of a formula
- rt = new XSSFRichTextString(_cell.getV());
- } else {
- rt = new XSSFRichTextString("");
- }
- } else if (xmlbeanCellType == STCellType.STR) {
- //cached formula value
- rt = new XSSFRichTextString(_cell.isSetV() ? _cell.getV() : "");
- } else {
- if (_cell.isSetV()) {
- try {
- int idx = Integer.parseInt(_cell.getV());
- rt = (XSSFRichTextString)_sharedStringSource.getItemAt(idx);
- } catch(Throwable t) {
- rt = new XSSFRichTextString("");
- }
- } else {
- rt = new XSSFRichTextString("");
- }
- }
- return rt;
- }
-
- @Override
- protected void setCellValueImpl(String value) {
- setCellValueImpl(new XSSFRichTextString(value));
- }
-
- @Override
- protected void setCellValueImpl(RichTextString str) {
- CellType cellType = getCellType();
- if (cellType == CellType.FORMULA) {
- _cell.setV(str.getString());
- _cell.setT(STCellType.STR);
- } else {
- if(_cell.getT() == STCellType.INLINE_STR) {
- //set the 'pre-evaluated result
- _cell.setV(str.getString());
- } else if (str instanceof XSSFRichTextString) {
- _cell.setT(STCellType.S);
- XSSFRichTextString rt = (XSSFRichTextString)str;
- rt.setStylesTableReference(_stylesSource);
- int sRef = _sharedStringSource.addSharedStringItem(rt);
- _cell.setV(Integer.toString(sRef));
- } else {
- _cell.setT(STCellType.S);
- XSSFRichTextString rt = new XSSFRichTextString(str.getString());
- rt.setStylesTableReference(_stylesSource);
- int sRef = _sharedStringSource.addSharedStringItem(rt);
- _cell.setV(Integer.toString(sRef));
- }
- }
- }
-
- /**
- * Return a formula for the cell, for example, {@code SUM(C4:E4)}
- *
- * @return a formula for the cell
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is not {@link CellType#FORMULA}
- */
- @Override
- public String getCellFormula() {
- // existing behavior - create a new XSSFEvaluationWorkbook for every call
- return getCellFormula(null);
- }
-
- /**
- * package/hierarchy use only - reuse an existing evaluation workbook if available for caching
- *
- * @param fpb evaluation workbook for reuse, if available, or null to create a new one as needed
- * @return a formula for the cell
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is not {@link CellType#FORMULA}
- */
- protected String getCellFormula(BaseXSSFEvaluationWorkbook fpb) {
- CellType cellType = getCellType();
- if(cellType != CellType.FORMULA) {
- throw typeMismatch(CellType.FORMULA, cellType, false);
- }
-
- CTCellFormula f = _cell.getF();
- if (isPartOfArrayFormulaGroup()) {
- /* In an excel generated array formula, the formula property might be set, but the string is empty in related cells */
- if (f == null || f.getStringValue().isEmpty()) {
- XSSFCell cell = getSheet().getFirstCellInArrayFormula(this);
- return cell.getCellFormula(fpb);
- }
- }
- if (f == null) {
- return null;
- } else if (f.getT() == STCellFormulaType.SHARED) {
- return convertSharedFormula(Math.toIntExact(f.getSi()),
- fpb == null ? XSSFEvaluationWorkbook.create(getSheet().getWorkbook()) : fpb);
- } else {
- return f.getStringValue();
- }
- }
-
- /**
- * Creates a non shared formula from the shared formula counterpart
- *
- * @param si Shared Group Index
- * @return non shared formula created for the given shared formula and this cell
- */
- private String convertSharedFormula(int si, BaseXSSFEvaluationWorkbook fpb){
- XSSFSheet sheet = getSheet();
-
- CTCellFormula f = sheet.getSharedFormula(si);
- if(f == null) {
- throw new IllegalStateException(
- "Master cell of a shared formula with sid="+si+" was not found");
- }
-
- String sharedFormula = f.getStringValue();
- //Range of cells which the shared formula applies to
- String sharedFormulaRange = f.getRef();
-
- CellRangeAddress ref = CellRangeAddress.valueOf(sharedFormulaRange);
-
- int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet);
- SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007);
-
- Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex, getRowIndex());
- Ptg[] fmla = sf.convertSharedFormulas(ptgs,
- getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn());
- return FormulaRenderer.toFormulaString(fpb, fmla);
- }
-
- /**
- * Sets formula for this cell.
- * <p>
- * Note, this method only sets the formula string and does not calculate the formula value.
- * To set the precalculated value use {@link #setCellValue(double)} or {@link #setCellValue(String)}
- * </p>
- * <p>
- * Note, if there are any shared formulas, his will invalidate any
- * {@link FormulaEvaluator} instances based on this workbook.
- * </p>
- *
- * @param formula the formula to set, e.g. {@code "SUM(C4:E4)"}.
- * If the argument is {@code null} then the current formula is removed.
- * @throws org.apache.poi.ss.formula.FormulaParseException if the formula has incorrect syntax or is otherwise invalid
- * @throws IllegalStateException if the operation is not allowed, for example,
- * when the cell is a part of a multi-cell array formula
- */
- @Override
- protected void setCellFormulaImpl(String formula) {
- assert formula != null;
- setFormula(formula, FormulaType.CELL);
- }
-
- /* package */ void setCellArrayFormula(String formula, CellRangeAddress range) {
- setFormula(formula, FormulaType.ARRAY);
- CTCellFormula cellFormula = _cell.getF();
- cellFormula.setT(STCellFormulaType.ARRAY);
- cellFormula.setRef(range.formatAsString());
- }
-
- private void setFormula(String formula, FormulaType formulaType) {
- XSSFWorkbook wb = _row.getSheet().getWorkbook();
- if (formulaType == FormulaType.ARRAY && formula == null) {
- removeFormulaImpl();
- return;
- }
-
- if (wb.getCellFormulaValidation()) {
- XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
- //validate through the FormulaParser
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex());
- // Make its format consistent with Excel.
- // eg: "SUM('Sheet1:Sheet2'!A1:B1)" will be trans to "SUM(Sheet1:Sheet2!A1:B1)"
- boolean hasError = false;
- for (Ptg ptg : ptgs) {
- if (ptg instanceof ErrPtg) {
- hasError = true;
- break;
- }
- }
- if (!hasError) {
- formula = FormulaRenderer.toFormulaString(fpb, ptgs);
- }
- }
-
- CTCellFormula f;
- if (_cell.isSetF()) {
- f = _cell.getF();
- f.setStringValue(formula);
- if(f.getT() == STCellFormulaType.SHARED){
- getRow().getSheet().onReadCell(this);
- }
- } else {
- f = CTCellFormula.Factory.newInstance();
- f.setStringValue(formula);
- _cell.setF(f);
- }
- }
-
- @Override
- protected void removeFormulaImpl() {
- _row.getSheet().getWorkbook().onDeleteFormula(this);
- if (_cell.isSetF()) {
- _row.getSheet().onDeleteFormula(this, null);
- _cell.unsetF();
- }
- }
-
- /**
- * Returns column index of this cell
- *
- * @return zero-based column index of a column in a sheet.
- */
- @Override
- public int getColumnIndex() {
- return this._cellNum;
- }
-
- /**
- * Returns row index of a row in the sheet that contains this cell
- *
- * @return zero-based row index of a row in the sheet that contains this cell
- */
- @Override
- public int getRowIndex() {
- return _row.getRowNum();
- }
-
- /**
- * Returns an A1 style reference to the location of this cell
- *
- * @return A1 style reference to the location of this cell
- */
- public String getReference() {
- String ref = _cell.getR();
- if(ref == null) {
- return getAddress().formatAsString();
- }
- return ref;
- }
-
- /**
- * Return the cell's style. Since POI v5.2.3, this returns the column style if the
- * cell has no style of its own. If no column default style is set, the row default style is checked.
- * This method has always fallen back to return the default style
- * if there is no other style to return.
- *
- * @return the cell's style.
- */
- @Override
- public XSSFCellStyle getCellStyle() {
- XSSFCellStyle style = getExplicitCellStyle();
- if (style == null) {
- style = getDefaultCellStyleFromColumn();
- }
- return style;
- }
-
- private XSSFCellStyle getExplicitCellStyle() {
- XSSFCellStyle style = null;
- if(_stylesSource.getNumCellStyles() > 0) {
- if (_cell.isSetS()) {
- long idx = _cell.getS();
- style = _stylesSource.getStyleAt(Math.toIntExact(idx));
- }
- }
- return style;
- }
-
- private XSSFCellStyle getDefaultCellStyleFromColumn() {
- XSSFCellStyle style = null;
- XSSFSheet sheet = getSheet();
- if (sheet != null) {
- style = (XSSFCellStyle) sheet.getColumnStyle(getColumnIndex());
- }
- return style;
- }
-
- protected void applyDefaultCellStyleIfNecessary() {
- XSSFCellStyle style = getExplicitCellStyle();
- if (style == null) {
- XSSFSheet sheet = getSheet();
- if (sheet != null) {
- XSSFCellStyle defaultStyle = getDefaultCellStyleFromColumn();
- if (defaultStyle != null) {
- setCellStyle(defaultStyle);
- }
- }
- }
- }
-
- /**
- * <p>Set the style for the cell. The style should be an XSSFCellStyle created/retrieved from
- * the XSSFWorkbook.</p>
- *
- * <p>To change the style of a cell without affecting other cells that use the same style,
- * use {@link org.apache.poi.ss.util.CellUtil#setCellStyleProperties(Cell, java.util.Map)}</p>
- *
- * @param style reference contained in the workbook.
- * If the value is null then the style information is removed causing the cell to used the default workbook style.
- * @throws IllegalArgumentException if style belongs to a different styles source (most likely because style is from a different Workbook)
- */
- @Override
- public void setCellStyle(CellStyle style) {
- if(style == null) {
- if(_cell.isSetS()) {
- _cell.unsetS();
- }
- } else {
- XSSFCellStyle xStyle = (XSSFCellStyle)style;
- xStyle.verifyBelongsToStylesSource(_stylesSource);
-
- long idx = _stylesSource.putStyle(xStyle);
- _cell.setS(idx);
- }
- }
-
- /**
- * POI currently supports these formula types:
- * <ul>
- * <li> {@link STCellFormulaType#NORMAL}
- * <li> {@link STCellFormulaType#SHARED}
- * <li> {@link STCellFormulaType#ARRAY}
- * </ul>
- * POI does not support {@link STCellFormulaType#DATA_TABLE} formulas.
- * @return true if the cell is of a formula type POI can handle
- */
- private boolean isFormulaCell() {
- return (_cell.isSetF() && _cell.getF().getT() != STCellFormulaType.DATA_TABLE)
- || getSheet().isCellInArrayFormulaContext(this);
- }
-
- /**
- * Return the cell type. Tables in an array formula return
- * {@link CellType#FORMULA} for all cells, even though the formula is only defined
- * in the OOXML file for the top left cell of the array.
- * <p>
- * NOTE: POI does not support data table formulas.
- * Cells in a data table appear to POI as plain cells typed from their cached value.
- *
- * @return the cell type
- */
- @Override
- public CellType getCellType() {
- if (isFormulaCell()) {
- return CellType.FORMULA;
- }
-
- return getBaseCellType(true);
- }
-
- /**
- * Only valid for formula cells
- * @return one of ({@link CellType#NUMERIC}, {@link CellType#STRING},
- * {@link CellType#BOOLEAN}, {@link CellType#ERROR}) depending
- * on the cached value of the formula
- */
- @Override
- public CellType getCachedFormulaResultType() {
- if (! isFormulaCell()) {
- throw new IllegalStateException("Only formula cells have cached results");
- }
-
- return getBaseCellType(false);
- }
-
- /**
- * Detect cell type based on the "t" attribute of the CTCell bean
- */
- private CellType getBaseCellType(boolean blankCells) {
- switch (_cell.getT().intValue()) {
- case STCellType.INT_B:
- return CellType.BOOLEAN;
- case STCellType.INT_N:
- if (!_cell.isSetV() && blankCells) {
- // ooxml does have a separate cell type of 'blank'. A blank cell gets encoded as
- // (either not present or) a numeric cell with no value set.
- // The formula evaluator (and perhaps other clients of this interface) needs to
- // distinguish blank values which sometimes get translated into zero and sometimes
- // empty string, depending on context
- return CellType.BLANK;
- }
- return CellType.NUMERIC;
- case STCellType.INT_E:
- return CellType.ERROR;
- case STCellType.INT_S: // String is in shared strings
- case STCellType.INT_INLINE_STR: // String is inline in cell
- case STCellType.INT_STR:
- return CellType.STRING;
- default:
- throw new IllegalStateException("Illegal cell type: " + this._cell.getT());
- }
- }
-
- /**
- * Get the value of the cell as a date.
- * <p>
- * For strings we throw an exception. For blank cells we return a null.
- * </p>
- * @return the value of the cell as a date
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is {@link CellType#STRING}
- * @throws NumberFormatException if the cell value isn't a parsable {@code double}.
- * @see DataFormatter for formatting this date into a string similar to how excel does.
- */
- @Override
- public Date getDateCellValue() {
- if (getCellType() == CellType.BLANK) {
- return null;
- }
-
- double value = getNumericCellValue();
- boolean date1904 = getSheet().getWorkbook().isDate1904();
- return DateUtil.getJavaDate(value, date1904);
- }
-
- /**
- * Get the value of the cell as a LocalDateTime.
- * <p>
- * For strings we throw an exception. For blank cells we return a null.
- * </p>
- * @return the value of the cell as a LocalDateTime
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is {@link CellType#STRING}
- * @throws NumberFormatException if the cell value isn't a parsable {@code double}.
- * @see DataFormatter for formatting this date into a string similar to how excel does.
- */
- @Override
- public LocalDateTime getLocalDateTimeCellValue() {
- if (getCellType() == CellType.BLANK) {
- return null;
- }
-
- double value = getNumericCellValue();
- boolean date1904 = getSheet().getWorkbook().isDate1904();
- return DateUtil.getLocalDateTime(value, date1904);
- }
-
- @Override
- protected void setCellValueImpl(Date value) {
- boolean date1904 = getSheet().getWorkbook().isDate1904();
- setCellValue(DateUtil.getExcelDate(value, date1904));
- }
-
- @Override
- protected void setCellValueImpl(LocalDateTime value) {
- boolean date1904 = getSheet().getWorkbook().isDate1904();
- setCellValue(DateUtil.getExcelDate(value, date1904));
- }
-
- @Override
- protected void setCellValueImpl(Calendar value) {
- boolean date1904 = getSheet().getWorkbook().isDate1904();
- setCellValue( DateUtil.getExcelDate(value, date1904 ));
- }
-
- /**
- * Returns the error message, such as #VALUE!
- *
- * @return the error message such as #VALUE!
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} isn't {@link CellType#ERROR}
- * @see FormulaError
- */
- public String getErrorCellString() throws IllegalStateException {
- CellType cellType = getBaseCellType(true);
- if(cellType != CellType.ERROR) {
- throw typeMismatch(CellType.ERROR, cellType, false);
- }
-
- return _cell.getV();
- }
- /**
- * Get the value of the cell as an error code.
- * <p>
- * For strings, numbers, and booleans, we throw an exception.
- * For blank cells we return a 0.
- * </p>
- *
- * @return the value of the cell as an error code
- * @throws IllegalStateException if the cell type returned by {@link #getCellType()} isn't {@link CellType #ERROR}
- * @see FormulaError
- */
- @Override
- public byte getErrorCellValue() throws IllegalStateException {
- String code = getErrorCellString();
- if (code == null) {
- return 0;
- }
- try {
- return FormulaError.forString(code).getCode();
- } catch (final IllegalArgumentException e) {
- throw new IllegalStateException("Unexpected error code", e);
- }
- }
-
- /**
- * Set a error value for the cell
- *
- * @param errorCode the error value to set this cell to. For formulas, we'll set the
- * precalculated value , for errors we'll set
- * its value. For other types, we will change the cell to an error
- * cell and set its value.
- * @see FormulaError
- */
- @Override
- public void setCellErrorValue(byte errorCode) {
- FormulaError error = FormulaError.forInt(errorCode);
- setCellErrorValue(error);
- }
-
- /**
- * Set a error value for the cell
- *
- * @param error the error value to set this cell to. For formulas, we'll set the
- * precalculated value , for errors we'll set
- * its value. For other types, we will change the cell to an error
- * cell and set its value.
- */
- public void setCellErrorValue(FormulaError error) {
- _cell.setT(STCellType.E);
- _cell.setV(error.getString());
- }
-
- @Override
- public void setAsActiveCell() {
- getSheet().setActiveCell(getAddress());
- }
-
- /**
- * Blanks this cell. Blank cells have no formula or value but may have styling.
- * This method erases all the data previously associated with this cell.
- */
- private void setBlankPrivate(){
- CTCell blank = CTCell.Factory.newInstance();
- blank.setR(_cell.getR());
- if(_cell.isSetS()) {
- blank.setS(_cell.getS());
- }
- _cell.set(blank);
- }
-
- /**
- * Sets column index of this cell
- *
- * @param num column index of this cell
- */
- protected void setCellNum(int num) {
- checkBounds(num);
- _cellNum = num;
- String ref = new CellReference(getRowIndex(), getColumnIndex()).formatAsString();
- _cell.setR(ref);
- }
-
- @Override
- protected void setCellTypeImpl(CellType cellType) {
- setCellType(cellType, null);
- }
-
- /**
- * Needed by bug #62834, which points out getCellFormula() expects an evaluation context or creates a new one,
- * so if there is one in use, it needs to be carried on through.
- * @param evalWb BaseXSSFEvaluationWorkbook already in use, or null if a new implicit one should be used
- */
- protected void setCellType(CellType cellType, BaseXSSFEvaluationWorkbook evalWb) {
- CellType prevType = getCellType();
- if(prevType == CellType.FORMULA && cellType != CellType.FORMULA) {
- if (_cell.isSetF()) {
- _row.getSheet().onDeleteFormula(this, evalWb);
- }
- getSheet().getWorkbook().onDeleteFormula(this);
- }
-
- switch (cellType) {
- case NUMERIC:
- _cell.setT(STCellType.N);
- break;
- case STRING:
- if(prevType != CellType.STRING){
- String str = convertCellValueToString();
- XSSFRichTextString rt = new XSSFRichTextString(str);
- rt.setStylesTableReference(_stylesSource);
- int sRef = _sharedStringSource.addSharedStringItem(rt);
- _cell.setV(Integer.toString(sRef));
- }
- _cell.setT(STCellType.S);
- break;
- case FORMULA:
- if(!_cell.isSetF()){
- CTCellFormula f = CTCellFormula.Factory.newInstance();
- f.setStringValue("0");
- _cell.setF(f);
- if(_cell.isSetT()) {
- _cell.unsetT();
- }
- }
- break;
- case BLANK:
- setBlankPrivate();
- break;
- case BOOLEAN:
- String newVal = convertCellValueToBoolean() ? TRUE_AS_STRING : FALSE_AS_STRING;
- _cell.setT(STCellType.B);
- _cell.setV(newVal);
- break;
-
- case ERROR:
- _cell.setT(STCellType.E);
- break;
-
-
- default:
- throw new IllegalArgumentException("Illegal cell type: " + cellType);
- }
- if (cellType != CellType.FORMULA && _cell.isSetF()) {
- _cell.unsetF();
- }
- }
-
- /**
- * Returns a string representation of the cell
- * <p>
- * Formula cells return the formula string, rather than the formula result.
- * Dates are displayed in dd-MMM-yyyy format
- * Errors are displayed as #ERR<errIdx>
- * </p>
- */
- @Override
- public String toString() {
- switch (getCellType()) {
- case NUMERIC:
- if (DateUtil.isCellDateFormatted(this)) {
- DateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", LocaleUtil.getUserLocale());
- sdf.setTimeZone(LocaleUtil.getUserTimeZone());
- return sdf.format(getDateCellValue());
- }
- return Double.toString(getNumericCellValue());
- case STRING:
- return getRichStringCellValue().toString();
- case FORMULA:
- return getCellFormula();
- case BLANK:
- return "";
- case BOOLEAN:
- return getBooleanCellValue() ? TRUE : FALSE;
- case ERROR:
- return ErrorEval.getText(getErrorCellValue());
- default:
- return "Unknown Cell Type: " + getCellType();
- }
- }
-
- /**
- * Returns the raw, underlying ooxml value for the cell
- * <p>
- * If the cell contains a string, then this value is an index into
- * the shared string table, pointing to the actual string value. Otherwise,
- * the value of the cell is expressed directly in this element. Cells containing formulas express
- * the last calculated result of the formula in this element.
- * </p>
- *
- * @return the raw cell value as contained in the underlying CTCell bean,
- * {@code null} for blank cells.
- */
- public String getRawValue() {
- return _cell.getV();
- }
-
-
- /**
- * Used to help format error messages
- */
- private static RuntimeException typeMismatch(CellType expectedType, CellType actualType, boolean isFormulaCell) {
- String msg = "Cannot get a " + expectedType + " value from a " + actualType+ " " + (isFormulaCell ? "formula " : "") + "cell";
- return new IllegalStateException(msg);
- }
-
- /**
- * @throws IllegalStateException if the bounds are exceeded.
- */
- private static void checkBounds(int cellIndex) {
- SpreadsheetVersion v = SpreadsheetVersion.EXCEL2007;
- int maxcol = SpreadsheetVersion.EXCEL2007.getLastColumnIndex();
- if (cellIndex < 0 || cellIndex > maxcol) {
- throw new IllegalArgumentException("Invalid column index (" + cellIndex
- + "). Allowable column range for " + v.name() + " is (0.."
- + maxcol + ") or ('A'..'" + v.getLastColumnName() + "')");
- }
- }
-
- /**
- * Returns cell comment associated with this cell
- *
- * @return the cell comment associated with this cell or {@code null}
- */
- @Override
- public XSSFComment getCellComment() {
- return getSheet().getCellComment(new CellAddress(this));
- }
-
- /**
- * Assign a comment to this cell. If the supplied comment is null,
- * the comment for this cell will be removed.
- *
- * @param comment the XSSFComment associated with this cell
- */
- @Override
- public void setCellComment(Comment comment) {
- if(comment == null) {
- removeCellComment();
- return;
- }
-
- comment.setAddress(getRowIndex(), getColumnIndex());
- }
-
- /**
- * Removes the comment for this cell, if there is one.
- */
- @Override
- public void removeCellComment() {
- XSSFComment comment = getCellComment();
- if(comment != null){
- CellAddress ref = new CellAddress(getReference());
- XSSFSheet sh = getSheet();
- sh.getCommentsTable(false).removeComment(ref);
- sh.getVMLDrawing(false).removeCommentShape(getRowIndex(), getColumnIndex());
- }
- }
-
- /**
- * Returns hyperlink associated with this cell
- *
- * @return hyperlink associated with this cell or {@code null} if not found
- */
- @Override
- public XSSFHyperlink getHyperlink() {
- return getSheet().getHyperlink(_row.getRowNum(), _cellNum);
- }
-
- /**
- * Assign a hyperlink to this cell. If the supplied hyperlink is null, the
- * hyperlink for this cell will be removed.
- *
- * @param hyperlink the hyperlink to associate with this cell
- */
- @Override
- public void setHyperlink(Hyperlink hyperlink) {
- if (hyperlink == null) {
- removeHyperlink();
- return;
- }
-
- XSSFHyperlink link;
- if (hyperlink instanceof XSSFHyperlink) {
- link = (XSSFHyperlink)hyperlink;
- } else {
- link = new XSSFHyperlink(hyperlink);
- }
-
- // Assign to us
- link.setCellReference( new CellReference(_row.getRowNum(), _cellNum).formatAsString() );
-
- // Add to the lists
- getSheet().addHyperlink(link);
- }
-
- /**
- * Removes the hyperlink for this cell, if there is one.
- */
- @Override
- public void removeHyperlink() {
- getSheet().removeHyperlink(_row.getRowNum(), _cellNum);
- }
-
- /**
- * Returns the xml bean containing information about the cell's location (reference), value,
- * data type, formatting, and formula
- *
- * @return the xml bean containing information about this cell
- */
- @Internal
- public CTCell getCTCell(){
- return _cell;
- }
-
- /**
- * Set a new internal xml bean. This is only for internal use, do not call this from outside!
- *
- * This is necessary in some rare cases to work around XMLBeans specialties.
- */
- @Internal
- public void setCTCell(CTCell cell) {
- _cell = cell;
- }
-
- /**
- * Chooses a new boolean value for the cell when its type is changing.<p>
- *
- * Usually the caller is calling setCellType() with the intention of calling
- * setCellValue(boolean) straight afterwards. This method only exists to give
- * the cell a somewhat reasonable value until the setCellValue() call (if at all).
- * TODO - perhaps a method like setCellTypeAndValue(CellType, Object) should be introduced to avoid this
- *
- * @throws IllegalStateException if cell type cannot be converted to boolean
- */
- private boolean convertCellValueToBoolean() {
- CellType cellType = getCellType();
-
- if (cellType == CellType.FORMULA) {
- cellType = getBaseCellType(false);
- }
-
- switch (cellType) {
- case BOOLEAN:
- return TRUE_AS_STRING.equals(_cell.getV());
- case STRING:
- int sstIndex = Integer.parseInt(_cell.getV());
- RichTextString rt = _sharedStringSource.getItemAt(sstIndex);
- String text = rt.getString();
- return Boolean.parseBoolean(text);
- case NUMERIC:
- return Double.parseDouble(_cell.getV()) != 0;
-
- case ERROR:
- // fall-through
- case BLANK:
- return false;
-
- default:
- throw new IllegalStateException("Unexpected cell type (" + cellType + ")");
- }
- }
-
- private String convertCellValueToString() {
- CellType cellType = getCellType();
-
- switch (cellType) {
- case BLANK:
- return "";
- case BOOLEAN:
- return TRUE_AS_STRING.equals(_cell.getV()) ? TRUE : FALSE;
- case STRING:
- try {
- int sstIndex = Integer.parseInt(_cell.getV());
- RichTextString rt = _sharedStringSource.getItemAt(sstIndex);
- return rt.getString();
- } catch (Throwable t) {
- return "";
- }
- case NUMERIC:
- case ERROR:
- return _cell.getV();
- case FORMULA:
- // should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator
- // just use cached formula result instead
- break;
- default:
- throw new IllegalStateException("Unexpected cell type (" + cellType + ")");
- }
- cellType = getBaseCellType(false);
- String textValue = _cell.getV();
- switch (cellType) {
- case BOOLEAN:
- if (TRUE_AS_STRING.equals(textValue)) {
- return TRUE;
- }
- if (FALSE_AS_STRING.equals(textValue)) {
- return FALSE;
- }
- throw new IllegalStateException("Unexpected boolean cached formula value '"
- + textValue + "'.");
-
- case STRING:
- // fall-through
- case NUMERIC:
- // fall-through
- case ERROR:
- return textValue;
-
- default:
- throw new IllegalStateException("Unexpected formula result type (" + cellType + ")");
- }
-
- }
-
- @Override
- public CellRangeAddress getArrayFormulaRange() {
- XSSFCell cell = getSheet().getFirstCellInArrayFormula(this);
- if (cell == null) {
- throw new IllegalStateException("Cell " + new CellReference(this).formatAsString()
- + " is not part of an array formula.");
- }
- String formulaRef = cell._cell.getF().getRef();
- return CellRangeAddress.valueOf(formulaRef);
- }
-
- @Override
- public boolean isPartOfArrayFormulaGroup() {
- return getSheet().isCellInArrayFormulaContext(this);
- }
-
- //Moved from XSSFRow.shift(). Not sure what is purpose.
- public void updateCellReferencesForShifting(String msg){
- if(isPartOfArrayFormulaGroup()) {
- tryToDeleteArrayFormula(msg);
- }
-
- CalculationChain calcChain = getSheet().getWorkbook().getCalculationChain();
- int sheetId = Math.toIntExact(getSheet().sheet.getSheetId());
-
- //remove the reference in the calculation chain
- if(calcChain != null) calcChain.removeItem(sheetId, getReference());
-
- CTCell ctCell = getCTCell();
- String r = new CellReference(getRowIndex(), getColumnIndex()).formatAsString();
- ctCell.setR(r);
- }
-
- }
|