| /* ==================================================================== |
| 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.hssf.record.aggregates; |
| |
| import org.apache.poi.hssf.record.ArrayRecord; |
| import org.apache.poi.hssf.record.CellValueRecordInterface; |
| import org.apache.poi.hssf.record.FormulaRecord; |
| import org.apache.poi.hssf.record.Record; |
| import org.apache.poi.hssf.record.SharedFormulaRecord; |
| import org.apache.poi.hssf.record.StringRecord; |
| import org.apache.poi.hssf.util.CellRangeAddress8Bit; |
| import org.apache.poi.ss.formula.Formula; |
| import org.apache.poi.ss.formula.ptg.ExpPtg; |
| import org.apache.poi.ss.formula.ptg.Ptg; |
| import org.apache.poi.ss.usermodel.FormulaError; |
| import org.apache.poi.ss.util.CellRangeAddress; |
| import org.apache.poi.ss.util.CellReference; |
| import org.apache.poi.util.RecordFormatException; |
| |
| /** |
| * The formula record aggregate is used to join together the formula record and it's |
| * (optional) string record and (optional) Shared Formula Record (template reads, excel optimization). |
| */ |
| public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { |
| |
| private final FormulaRecord _formulaRecord; |
| private SharedValueManager _sharedValueManager; |
| /** caches the calculated result of the formula */ |
| private StringRecord _stringRecord; |
| private SharedFormulaRecord _sharedFormulaRecord; |
| |
| /** |
| * @param stringRec may be <code>null</code> if this formula does not have a cached text |
| * value. |
| * @param svm the {@link SharedValueManager} for the current sheet |
| */ |
| public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) { |
| if (svm == null) { |
| throw new IllegalArgumentException("sfm must not be null"); |
| } |
| if (formulaRec.hasCachedResultString()) { |
| if (stringRec == null) { |
| throw new RecordFormatException("Formula record flag is set but String record was not found"); |
| } |
| _stringRecord = stringRec; |
| } else { |
| // Usually stringRec is null here (in agreement with what the formula rec says). |
| // In the case where an extra StringRecord is erroneously present, Excel (2007) |
| // ignores it (see bug 46213). |
| _stringRecord = null; |
| } |
| |
| _formulaRecord = formulaRec; |
| _sharedValueManager = svm; |
| if (formulaRec.isSharedFormula()) { |
| CellReference firstCell = formulaRec.getFormula().getExpReference(); |
| if (firstCell == null) { |
| handleMissingSharedFormulaRecord(formulaRec); |
| } else { |
| _sharedFormulaRecord = svm.linkSharedFormulaRecord(firstCell, this); |
| } |
| } |
| } |
| |
| /** |
| * Sometimes the shared formula flag "seems" to be erroneously set (because the corresponding |
| * {@link SharedFormulaRecord} does not exist). Normally this would leave no way of determining |
| * the {@link Ptg} tokens for the formula. However as it turns out in these |
| * cases, Excel encodes the unshared {@link Ptg} tokens in the right place (inside the {@link |
| * FormulaRecord}). So the the only thing that needs to be done is to ignore the erroneous |
| * shared formula flag.<br> |
| * |
| * This method may also be used for setting breakpoints to help diagnose issues regarding the |
| * abnormally-set 'shared formula' flags. |
| * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p> |
| */ |
| private static void handleMissingSharedFormulaRecord(FormulaRecord formula) { |
| // make sure 'unshared' formula is actually available |
| Ptg firstToken = formula.getParsedExpression()[0]; |
| if (firstToken instanceof ExpPtg) { |
| throw new RecordFormatException( |
| "SharedFormulaRecord not found for FormulaRecord with (isSharedFormula=true)"); |
| } |
| // could log an info message here since this is a fairly unusual occurrence. |
| formula.setSharedFormula(false); // no point leaving the flag erroneously set |
| } |
| |
| public FormulaRecord getFormulaRecord() { |
| return _formulaRecord; |
| } |
| |
| /** |
| * debug only |
| * TODO - encapsulate |
| */ |
| public StringRecord getStringRecord() { |
| return _stringRecord; |
| } |
| |
| public short getXFIndex() { |
| return _formulaRecord.getXFIndex(); |
| } |
| |
| public void setXFIndex(short xf) { |
| _formulaRecord.setXFIndex(xf); |
| } |
| |
| public void setColumn(short col) { |
| _formulaRecord.setColumn(col); |
| } |
| |
| public void setRow(int row) { |
| _formulaRecord.setRow(row); |
| } |
| |
| public short getColumn() { |
| return _formulaRecord.getColumn(); |
| } |
| |
| public int getRow() { |
| return _formulaRecord.getRow(); |
| } |
| |
| public String toString() { |
| return _formulaRecord.toString(); |
| } |
| |
| public void visitContainedRecords(RecordVisitor rv) { |
| rv.visitRecord(_formulaRecord); |
| Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(this); |
| if (sharedFormulaRecord != null) { |
| rv.visitRecord(sharedFormulaRecord); |
| } |
| if (_formulaRecord.hasCachedResultString() && _stringRecord != null) { |
| rv.visitRecord(_stringRecord); |
| } |
| } |
| |
| public String getStringValue() { |
| if(_stringRecord==null) { |
| return null; |
| } |
| return _stringRecord.getString(); |
| } |
| |
| public void setCachedStringResult(String value) { |
| |
| // Save the string into a String Record, creating one if required |
| if(_stringRecord == null) { |
| _stringRecord = new StringRecord(); |
| } |
| _stringRecord.setString(value); |
| if (value.length() < 1) { |
| _formulaRecord.setCachedResultTypeEmptyString(); |
| } else { |
| _formulaRecord.setCachedResultTypeString(); |
| } |
| } |
| public void setCachedBooleanResult(boolean value) { |
| _stringRecord = null; |
| _formulaRecord.setCachedResultBoolean(value); |
| } |
| public void setCachedErrorResult(int errorCode) { |
| _stringRecord = null; |
| _formulaRecord.setCachedResultErrorCode(errorCode); |
| } |
| public void setCachedErrorResult(FormulaError error) { |
| setCachedErrorResult(error.getCode()); |
| } |
| public void setCachedDoubleResult(double value) { |
| _stringRecord = null; |
| _formulaRecord.setValue(value); |
| } |
| |
| public Ptg[] getFormulaTokens() { |
| if (_sharedFormulaRecord != null) { |
| return _sharedFormulaRecord.getFormulaTokens(_formulaRecord); |
| } |
| CellReference expRef = _formulaRecord.getFormula().getExpReference(); |
| if (expRef != null) { |
| ArrayRecord arec = _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol()); |
| return arec.getFormulaTokens(); |
| } |
| return _formulaRecord.getParsedExpression(); |
| } |
| |
| /** |
| * Also checks for a related shared formula and unlinks it if found |
| */ |
| public void setParsedExpression(Ptg[] ptgs) { |
| notifyFormulaChanging(); |
| _formulaRecord.setParsedExpression(ptgs); |
| } |
| |
| public void unlinkSharedFormula() { |
| SharedFormulaRecord sfr = _sharedFormulaRecord; |
| if (sfr == null) { |
| throw new IllegalStateException("Formula not linked to shared formula"); |
| } |
| Ptg[] ptgs = sfr.getFormulaTokens(_formulaRecord); |
| _formulaRecord.setParsedExpression(ptgs); |
| //Now its not shared! |
| _formulaRecord.setSharedFormula(false); |
| _sharedFormulaRecord = null; |
| } |
| /** |
| * Should be called by any code which is either deleting this formula cell, or changing |
| * its type. This method gives the aggregate a chance to unlink any shared formula |
| * that may be involved with this cell formula. |
| */ |
| public void notifyFormulaChanging() { |
| if (_sharedFormulaRecord != null) { |
| _sharedValueManager.unlink(_sharedFormulaRecord); |
| } |
| } |
| public boolean isPartOfArrayFormula() { |
| if (_sharedFormulaRecord != null) { |
| return false; |
| } |
| CellReference expRef = _formulaRecord.getFormula().getExpReference(); |
| ArrayRecord arec = expRef == null ? null : _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol()); |
| return arec != null; |
| } |
| |
| public CellRangeAddress getArrayFormulaRange() { |
| if (_sharedFormulaRecord != null) { |
| throw new IllegalStateException("not an array formula cell."); |
| } |
| CellReference expRef = _formulaRecord.getFormula().getExpReference(); |
| if (expRef == null) { |
| throw new IllegalStateException("not an array formula cell."); |
| } |
| ArrayRecord arec = _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol()); |
| if (arec == null) { |
| throw new IllegalStateException("ArrayRecord was not found for the locator " + expRef.formatAsString()); |
| } |
| CellRangeAddress8Bit a = arec.getRange(); |
| return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(),a.getLastColumn()); |
| } |
| |
| public void setArrayFormula(CellRangeAddress r, Ptg[] ptgs) { |
| |
| ArrayRecord arr = new ArrayRecord(Formula.create(ptgs), new CellRangeAddress8Bit(r.getFirstRow(), r.getLastRow(), r.getFirstColumn(), r.getLastColumn())); |
| _sharedValueManager.addArrayRecord(arr); |
| } |
| /** |
| * Removes an array formula |
| * @return the range of the array formula containing the specified cell. Never <code>null</code> |
| */ |
| public CellRangeAddress removeArrayFormula(int rowIndex, int columnIndex) { |
| CellRangeAddress8Bit a = _sharedValueManager.removeArrayFormula(rowIndex, columnIndex); |
| // at this point FormulaRecordAggregate#isPartOfArrayFormula() should return false |
| _formulaRecord.setParsedExpression(null); |
| return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(), a.getLastColumn()); |
| } |
| } |