Fixed problem with linking shared formulas when ranges overlap
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@712307 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
2517aa0e42
commit
612008386a
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<!-- Don't forget to update status.xml too! -->
|
<!-- Don't forget to update status.xml too! -->
|
||||||
<release version="3.5-beta4" date="2008-??-??">
|
<release version="3.5-beta4" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">Fixed problem with linking shared formulas when ranges overlap</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45784 - More fixes to SeriesTextRecord</action>
|
<action dev="POI-DEVELOPERS" type="fix">45784 - More fixes to SeriesTextRecord</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46033 - fixed TableCell to correctly set text type</action>
|
<action dev="POI-DEVELOPERS" type="fix">46033 - fixed TableCell to correctly set text type</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46122 - fixed Picture.draw to skip rendering if picture data was not found</action>
|
<action dev="POI-DEVELOPERS" type="fix">46122 - fixed Picture.draw to skip rendering if picture data was not found</action>
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
<!-- Don't forget to update changes.xml too! -->
|
<!-- Don't forget to update changes.xml too! -->
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.5-beta4" date="2008-??-??">
|
<release version="3.5-beta4" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">Fixed problem with linking shared formulas when ranges overlap</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45784 - More fixes to SeriesTextRecord</action>
|
<action dev="POI-DEVELOPERS" type="fix">45784 - More fixes to SeriesTextRecord</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46033 - fixed TableCell to correctly set text type</action>
|
<action dev="POI-DEVELOPERS" type="fix">46033 - fixed TableCell to correctly set text type</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46122 - fixed Picture.draw to skip rendering if picture data was not found</action>
|
<action dev="POI-DEVELOPERS" type="fix">46122 - fixed Picture.draw to skip rendering if picture data was not found</action>
|
||||||
|
@ -345,6 +345,10 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
|
|||||||
return field_8_parsed_expr.getTokens();
|
return field_8_parsed_expr.getTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Formula getFormula() {
|
||||||
|
return field_8_parsed_expr;
|
||||||
|
}
|
||||||
|
|
||||||
public void setParsedExpression(Ptg[] ptgs) {
|
public void setParsedExpression(Ptg[] ptgs) {
|
||||||
field_8_parsed_expr = Formula.create(ptgs);
|
field_8_parsed_expr = Formula.create(ptgs);
|
||||||
}
|
}
|
||||||
|
@ -182,4 +182,7 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
|
|||||||
result.field_7_parsed_expr = field_7_parsed_expr.copy();
|
result.field_7_parsed_expr = field_7_parsed_expr.copy();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
public boolean isFormulaSame(SharedFormulaRecord other) {
|
||||||
|
return field_7_parsed_expr.isSame(other.field_7_parsed_expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ import org.apache.poi.hssf.record.SharedFormulaRecord;
|
|||||||
import org.apache.poi.hssf.record.StringRecord;
|
import org.apache.poi.hssf.record.StringRecord;
|
||||||
import org.apache.poi.hssf.record.formula.ExpPtg;
|
import org.apache.poi.hssf.record.formula.ExpPtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ptg;
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
import org.apache.poi.ss.formula.Formula;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The formula record aggregate is used to join together the formula record and it's
|
* The formula record aggregate is used to join together the formula record and it's
|
||||||
@ -57,15 +59,19 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
|
|||||||
+ (hasCachedStringFlag ? "" : "not ") + " set");
|
+ (hasCachedStringFlag ? "" : "not ") + " set");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formulaRec.isSharedFormula()) {
|
||||||
|
CellReference cr = new CellReference(formulaRec.getRow(), formulaRec.getColumn());
|
||||||
|
|
||||||
|
CellReference firstCell = formulaRec.getFormula().getExpReference();
|
||||||
|
if (firstCell == null) {
|
||||||
|
handleMissingSharedFormulaRecord(formulaRec);
|
||||||
|
} else {
|
||||||
|
_sharedFormulaRecord = svm.linkSharedFormulaRecord(firstCell, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
_formulaRecord = formulaRec;
|
_formulaRecord = formulaRec;
|
||||||
_sharedValueManager = svm;
|
_sharedValueManager = svm;
|
||||||
_stringRecord = stringRec;
|
_stringRecord = stringRec;
|
||||||
if (formulaRec.isSharedFormula()) {
|
|
||||||
_sharedFormulaRecord = svm.linkSharedFormulaRecord(this);
|
|
||||||
if (_sharedFormulaRecord == null) {
|
|
||||||
handleMissingSharedFormulaRecord(formulaRec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sometimes the shared formula flag "seems" to be erroneously set (because the corresponding
|
* Sometimes the shared formula flag "seems" to be erroneously set (because the corresponding
|
||||||
@ -132,10 +138,14 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
|
|||||||
|
|
||||||
public void visitContainedRecords(RecordVisitor rv) {
|
public void visitContainedRecords(RecordVisitor rv) {
|
||||||
rv.visitRecord(_formulaRecord);
|
rv.visitRecord(_formulaRecord);
|
||||||
// TODO - only bother with this if array or table formula
|
CellReference sharedFirstCell = _formulaRecord.getFormula().getExpReference();
|
||||||
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
|
// perhaps this could be optimised by consulting the (somewhat unreliable) isShared flag
|
||||||
if (sharedFormulaRecord != null) {
|
// and/or distinguishing between tExp and tTbl.
|
||||||
rv.visitRecord(sharedFormulaRecord);
|
if (sharedFirstCell != null) {
|
||||||
|
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(sharedFirstCell, this);
|
||||||
|
if (sharedFormulaRecord != null) {
|
||||||
|
rv.visitRecord(sharedFormulaRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_stringRecord != null) {
|
if (_stringRecord != null) {
|
||||||
rv.visitRecord(_stringRecord);
|
rv.visitRecord(_stringRecord);
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.apache.poi.hssf.record.aggregates;
|
package org.apache.poi.hssf.record.aggregates;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -25,6 +27,10 @@ import org.apache.poi.hssf.record.FormulaRecord;
|
|||||||
import org.apache.poi.hssf.record.SharedFormulaRecord;
|
import org.apache.poi.hssf.record.SharedFormulaRecord;
|
||||||
import org.apache.poi.hssf.record.SharedValueRecordBase;
|
import org.apache.poi.hssf.record.SharedValueRecordBase;
|
||||||
import org.apache.poi.hssf.record.TableRecord;
|
import org.apache.poi.hssf.record.TableRecord;
|
||||||
|
import org.apache.poi.hssf.record.formula.ExpPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.TblPtg;
|
||||||
|
import org.apache.poi.hssf.util.CellRangeAddress8Bit;
|
||||||
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages various auxiliary records while constructing a
|
* Manages various auxiliary records while constructing a
|
||||||
@ -34,15 +40,15 @@ import org.apache.poi.hssf.record.TableRecord;
|
|||||||
* <li>{@link ArrayRecord}s</li>
|
* <li>{@link ArrayRecord}s</li>
|
||||||
* <li>{@link TableRecord}s</li>
|
* <li>{@link TableRecord}s</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
public final class SharedValueManager {
|
public final class SharedValueManager {
|
||||||
|
|
||||||
// This class should probably be generalised to handle array and table groups too
|
// This class should probably be generalised to handle array and table groups too
|
||||||
private static final class SharedValueGroup {
|
private static final class SharedValueGroup {
|
||||||
private final SharedValueRecordBase _svr;
|
private final SharedValueRecordBase _svr;
|
||||||
private final FormulaRecordAggregate[] _frAggs;
|
private FormulaRecordAggregate[] _frAggs;
|
||||||
private int _numberOfFormulas;
|
private int _numberOfFormulas;
|
||||||
|
|
||||||
public SharedValueGroup(SharedValueRecordBase svr) {
|
public SharedValueGroup(SharedValueRecordBase svr) {
|
||||||
@ -54,6 +60,12 @@ public final class SharedValueManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void add(FormulaRecordAggregate agg) {
|
public void add(FormulaRecordAggregate agg) {
|
||||||
|
if (_numberOfFormulas >= _frAggs.length) {
|
||||||
|
// this probably shouldn't occur - problems with sample file "15228.xls"
|
||||||
|
FormulaRecordAggregate[] temp = new FormulaRecordAggregate[_numberOfFormulas*2];
|
||||||
|
System.arraycopy(_frAggs, 0, temp, 0, _frAggs.length);
|
||||||
|
_frAggs = temp;
|
||||||
|
}
|
||||||
_frAggs[_numberOfFormulas++] = agg;
|
_frAggs[_numberOfFormulas++] = agg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +75,6 @@ public final class SharedValueManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInRange(int rowIx, int columnIx) {
|
|
||||||
return _svr.isInRange(rowIx, columnIx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SharedValueRecordBase getSVR() {
|
public SharedValueRecordBase getSVR() {
|
||||||
return _svr;
|
return _svr;
|
||||||
}
|
}
|
||||||
@ -77,13 +85,16 @@ public final class SharedValueManager {
|
|||||||
* that is not the top-left corner of the range.
|
* that is not the top-left corner of the range.
|
||||||
* @return <code>true</code> if this is the first formula cell in the group
|
* @return <code>true</code> if this is the first formula cell in the group
|
||||||
*/
|
*/
|
||||||
public boolean isFirstCell(int row, int column) {
|
public boolean isFirstMember(FormulaRecordAggregate agg) {
|
||||||
// hack for the moment, just check against the first formula that
|
return agg == _frAggs[0];
|
||||||
// came in through the add() method.
|
}
|
||||||
FormulaRecordAggregate fra = _frAggs[0];
|
public final String toString() {
|
||||||
return fra.getRow() == row && fra.getColumn() == column;
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(_svr.getRange().toString());
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final SharedValueManager EMPTY = new SharedValueManager(
|
public static final SharedValueManager EMPTY = new SharedValueManager(
|
||||||
@ -106,6 +117,13 @@ public final class SharedValueManager {
|
|||||||
_groupsBySharedFormulaRecord = m;
|
_groupsBySharedFormulaRecord = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recs list of sheet records (possibly contains records for other parts of the Excel file)
|
||||||
|
* @param startIx index of first row/cell record for current sheet
|
||||||
|
* @param endIx one past index of last row/cell record for current sheet. It is important
|
||||||
|
* that this code does not inadvertently collect <tt>SharedFormulaRecord</tt>s from any other
|
||||||
|
* sheet (which could happen if endIx is chosen poorly). (see bug 44449)
|
||||||
|
*/
|
||||||
public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords,
|
public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords,
|
||||||
ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
|
ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
|
||||||
if (sharedFormulaRecords.length + arrayRecords.length + tableRecords.length < 1) {
|
if (sharedFormulaRecords.length + arrayRecords.length + tableRecords.length < 1) {
|
||||||
@ -116,77 +134,145 @@ public final class SharedValueManager {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return <code>null</code> if the specified formula does not have any corresponding
|
* @param firstCell as extracted from the {@link ExpPtg} from the cell's formula.
|
||||||
* {@link SharedFormulaRecord}
|
* @return never <code>null</code>
|
||||||
*/
|
*/
|
||||||
public SharedFormulaRecord linkSharedFormulaRecord(FormulaRecordAggregate agg) {
|
public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) {
|
||||||
FormulaRecord formula = agg.getFormulaRecord();
|
|
||||||
int row = formula.getRow();
|
SharedValueGroup result = findGroup(getGroups(), firstCell);
|
||||||
int column = formula.getColumn();
|
result.add(agg);
|
||||||
// Traverse the list of shared formulas in
|
return (SharedFormulaRecord) result.getSVR();
|
||||||
// reverse order, and try to find the correct one
|
}
|
||||||
// for us
|
|
||||||
|
private static SharedValueGroup findGroup(SharedValueGroup[] groups, CellReference firstCell) {
|
||||||
SharedValueGroup[] groups = getGroups();
|
int row = firstCell.getRow();
|
||||||
|
int column = firstCell.getCol();
|
||||||
|
// Traverse the list of shared formulas and try to find the correct one for us
|
||||||
|
|
||||||
|
// perhaps this could be optimised to some kind of binary search
|
||||||
for (int i = 0; i < groups.length; i++) {
|
for (int i = 0; i < groups.length; i++) {
|
||||||
SharedValueGroup svr = groups[i];
|
SharedValueGroup svg = groups[i];
|
||||||
if (svr.isInRange(row, column)) {
|
if (svg.getSVR().isFirstCell(row, column)) {
|
||||||
svr.add(agg);
|
return svg;
|
||||||
return (SharedFormulaRecord) svr.getSVR();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
// else - no SharedFormulaRecord was found with the specified firstCell.
|
||||||
|
// This is unusual, but one sample file exhibits the anomaly: "ex45046-21984.xls"
|
||||||
|
// Excel seems to handle the problem OK, and doesn't even correct it. Perhaps POI should.
|
||||||
|
|
||||||
|
// search for shared formula by range
|
||||||
|
SharedValueGroup result = null;
|
||||||
|
for (int i = 0; i < groups.length; i++) {
|
||||||
|
SharedValueGroup svg = groups[i];
|
||||||
|
if (svg.getSVR().isInRange(row, column)) {
|
||||||
|
if (result != null) {
|
||||||
|
// This happens in sample file "15228.xls"
|
||||||
|
if (sharedFormulasAreSame(result, svg)) {
|
||||||
|
// hopefully this is OK - just use the first one since they are the same
|
||||||
|
// not quite
|
||||||
|
// TODO - fix file "15228.xls" so it opens in Excel after rewriting with POI
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("This cell is in the range of more than one distinct shared formula");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = svg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
throw new RuntimeException("Failed to find a matching shared formula record");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the ugly situation (seen in example "15228.xls") where a shared formula cell is
|
||||||
|
* covered by more than one shared formula range, but the formula cell's {@link ExpPtg}
|
||||||
|
* doesn't identify any of them.
|
||||||
|
* @return <code>true</code> if the underlying shared formulas are the same
|
||||||
|
*/
|
||||||
|
private static boolean sharedFormulasAreSame(SharedValueGroup grpA, SharedValueGroup grpB) {
|
||||||
|
// safe to cast here because this findGroup() is never called for ARRAY or TABLE formulas
|
||||||
|
SharedFormulaRecord sfrA = (SharedFormulaRecord) grpA.getSVR();
|
||||||
|
SharedFormulaRecord sfrB = (SharedFormulaRecord) grpB.getSVR();
|
||||||
|
return sfrA.isFormulaSame(sfrB);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SharedValueGroup[] getGroups() {
|
private SharedValueGroup[] getGroups() {
|
||||||
if (_groups == null) {
|
if (_groups == null) {
|
||||||
SharedValueGroup[] groups = new SharedValueGroup[_groupsBySharedFormulaRecord.size()];
|
SharedValueGroup[] groups = new SharedValueGroup[_groupsBySharedFormulaRecord.size()];
|
||||||
_groupsBySharedFormulaRecord.values().toArray(groups);
|
_groupsBySharedFormulaRecord.values().toArray(groups);
|
||||||
|
Arrays.sort(groups, SVGComparator); // make search behaviour more deterministic
|
||||||
_groups = groups;
|
_groups = groups;
|
||||||
|
|
||||||
}
|
}
|
||||||
return _groups;
|
return _groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Comparator SVGComparator = new Comparator() {
|
||||||
|
|
||||||
|
public int compare(Object a, Object b) {
|
||||||
|
CellRangeAddress8Bit rangeA = ((SharedValueGroup)a).getSVR().getRange();
|
||||||
|
CellRangeAddress8Bit rangeB = ((SharedValueGroup)b).getSVR().getRange();
|
||||||
|
|
||||||
|
int cmp;
|
||||||
|
cmp = rangeA.getFirstRow() - rangeB.getFirstRow();
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
cmp = rangeA.getFirstColumn() - rangeB.getFirstColumn();
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note - does not return SharedFormulaRecords currently, because the corresponding formula
|
* The {@link SharedValueRecordBase} record returned by this method
|
||||||
* records have been converted to 'unshared'. POI does not attempt to re-share formulas. On
|
* @param firstCell the cell coordinates as read from the {@link ExpPtg} or {@link TblPtg}
|
||||||
* the other hand, there is no such conversion for array or table formulas, so this method
|
* of the current formula. Note - this is usually not the same as the cell coordinates
|
||||||
* returns the TABLE or ARRAY record (if it should be written after the specified
|
* of the formula's cell.
|
||||||
* formulaRecord.
|
*
|
||||||
*
|
* @return the SHRFMLA, TABLE or ARRAY record for this formula cell, if it is the first cell of a
|
||||||
* @return the TABLE or ARRAY record for this formula cell, if it is the first cell of a
|
* table or array region. <code>null</code> if
|
||||||
* table or array region.
|
|
||||||
*/
|
*/
|
||||||
public SharedValueRecordBase getRecordForFirstCell(FormulaRecord formulaRecord) {
|
public SharedValueRecordBase getRecordForFirstCell(CellReference firstCell, FormulaRecordAggregate agg) {
|
||||||
int row = formulaRecord.getRow();
|
int row = firstCell.getRow();
|
||||||
int column = formulaRecord.getColumn();
|
int column = firstCell.getCol();
|
||||||
for (int i = 0; i < _tableRecords.length; i++) {
|
boolean isTopLeft = agg.getRow() == row && agg.getColumn() == column;
|
||||||
TableRecord tr = _tableRecords[i];
|
if (isTopLeft) {
|
||||||
if (tr.isFirstCell(row, column)) {
|
for (int i = 0; i < _tableRecords.length; i++) {
|
||||||
return tr;
|
TableRecord tr = _tableRecords[i];
|
||||||
|
if (tr.isFirstCell(row, column)) {
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for (int i = 0; i < _arrayRecords.length; i++) {
|
||||||
for (int i = 0; i < _arrayRecords.length; i++) {
|
ArrayRecord ar = _arrayRecords[i];
|
||||||
ArrayRecord ar = _arrayRecords[i];
|
if (ar.isFirstCell(row, column)) {
|
||||||
if (ar.isFirstCell(row, column)) {
|
return ar;
|
||||||
return ar;
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Since arrays and tables cannot be sparse (all cells in range participate)
|
||||||
|
// no need to search arrays and tables if agg is not the top left cell
|
||||||
}
|
}
|
||||||
SharedValueGroup[] groups = getGroups();
|
SharedValueGroup[] groups = getGroups();
|
||||||
for (int i = 0; i < groups.length; i++) {
|
for (int i = 0; i < groups.length; i++) {
|
||||||
SharedValueGroup svg = groups[i];
|
SharedValueGroup svg = groups[i];
|
||||||
if (svg.isFirstCell(row, column)) {
|
SharedValueRecordBase svr = svg.getSVR();
|
||||||
return svg.getSVR();
|
if (svr.isFirstCell(row, column)) {
|
||||||
|
if (svg.isFirstMember(agg)) {
|
||||||
|
return svr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt>
|
* Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt>
|
||||||
* to plain unshared formulas
|
* to plain unshared formulas
|
||||||
*/
|
*/
|
||||||
public void unlink(SharedFormulaRecord sharedFormulaRecord) {
|
public void unlink(SharedFormulaRecord sharedFormulaRecord) {
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
package org.apache.poi.ss.formula;
|
package org.apache.poi.ss.formula;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.ArrayRecord;
|
||||||
|
import org.apache.poi.hssf.record.SharedFormulaRecord;
|
||||||
|
import org.apache.poi.hssf.record.TableRecord;
|
||||||
|
import org.apache.poi.hssf.record.formula.ExpPtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ptg;
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.hssf.record.formula.TblPtg;
|
||||||
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
import org.apache.poi.util.LittleEndianInput;
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
import org.apache.poi.util.LittleEndianOutput;
|
import org.apache.poi.util.LittleEndianOutput;
|
||||||
@ -130,4 +139,35 @@ public class Formula {
|
|||||||
// OK to return this for the moment because currently immutable
|
// OK to return this for the moment because currently immutable
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the locator for the corresponding {@link SharedFormulaRecord}, {@link ArrayRecord} or
|
||||||
|
* {@link TableRecord} if this formula belongs to such a grouping. The {@link CellReference}
|
||||||
|
* returned by this method will match the top left corner of the range of that grouping.
|
||||||
|
* The return value is usually not the same as the location of the cell containing this formula.
|
||||||
|
*
|
||||||
|
* @return the firstRow & firstColumn of an array formula or shared formula that this formula
|
||||||
|
* belongs to. <code>null</code> if this formula is not part of an array or shared formula.
|
||||||
|
*/
|
||||||
|
public CellReference getExpReference() {
|
||||||
|
byte[] data = _byteEncoding;
|
||||||
|
if (data.length != 5) {
|
||||||
|
// tExp and tTbl are always 5 bytes long, and the only ptg in the formula
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
switch (data[0]) {
|
||||||
|
case ExpPtg.sid:
|
||||||
|
break;
|
||||||
|
case TblPtg.sid:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int firstRow = LittleEndian.getUShort(data, 1);
|
||||||
|
int firstColumn = LittleEndian.getUShort(data, 3);
|
||||||
|
return new CellReference(firstRow, firstColumn);
|
||||||
|
}
|
||||||
|
public boolean isSame(Formula other) {
|
||||||
|
return Arrays.equals(_byteEncoding, other._byteEncoding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,7 @@ public abstract class CellRangeAddressBase {
|
|||||||
_firstCol = firstCol;
|
_firstCol = firstCol;
|
||||||
_lastCol = lastCol;
|
_lastCol = lastCol;
|
||||||
}
|
}
|
||||||
private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn)
|
private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn) {
|
||||||
{
|
|
||||||
if(lastRow < 0 || lastRow > LAST_ROW_INDEX) {
|
if(lastRow < 0 || lastRow > LAST_ROW_INDEX) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -129,6 +128,8 @@ public abstract class CellRangeAddressBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return getClass().getName() + " ["+_firstRow+", "+_lastRow+", "+_firstCol+", "+_lastCol+"]";
|
CellReference crA = new CellReference(_firstRow, _firstCol);
|
||||||
|
CellReference crB = new CellReference(_lastRow, _lastCol);
|
||||||
|
return getClass().getName() + " [" + crA.formatAsString() + ":" + crB.formatAsString() +"]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
src/testcases/org/apache/poi/hssf/data/overlapSharedFormula.xls
Normal file
BIN
src/testcases/org/apache/poi/hssf/data/overlapSharedFormula.xls
Normal file
Binary file not shown.
@ -34,6 +34,7 @@ public final class AllRecordAggregateTests {
|
|||||||
result.addTestSuite(TestColumnInfoRecordsAggregate.class);
|
result.addTestSuite(TestColumnInfoRecordsAggregate.class);
|
||||||
result.addTestSuite(TestFormulaRecordAggregate.class);
|
result.addTestSuite(TestFormulaRecordAggregate.class);
|
||||||
result.addTestSuite(TestRowRecordsAggregate.class);
|
result.addTestSuite(TestRowRecordsAggregate.class);
|
||||||
|
result.addTestSuite(TestSharedValueManager.class);
|
||||||
result.addTestSuite(TestValueRecordsAggregate.class);
|
result.addTestSuite(TestValueRecordsAggregate.class);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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 java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||||
|
import org.apache.poi.hssf.record.Record;
|
||||||
|
import org.apache.poi.hssf.record.SharedFormulaRecord;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.hssf.usermodel.RecordInspector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SharedValueManager}
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class TestSharedValueManager extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Excel workbook contains two sheets that each have a pair of overlapping shared formula
|
||||||
|
* ranges. The first sheet has one row and one column shared formula ranges which intersect.
|
||||||
|
* The second sheet has two column shared formula ranges - one contained within the other.
|
||||||
|
* These shared formula ranges were created by fill-dragging a single cell formula across the
|
||||||
|
* desired region. The larger shared formula ranges were placed first.<br/>
|
||||||
|
*
|
||||||
|
* There are probably many ways to produce similar effects, but it should be noted that Excel
|
||||||
|
* is quite temperamental in this regard. Slight variations in technique can cause the shared
|
||||||
|
* formulas to spill out into plain formula records (which would make these tests pointless).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final String SAMPLE_FILE_NAME = "overlapSharedFormula.xls";
|
||||||
|
/**
|
||||||
|
* Some of these bugs are intermittent, and the test author couldn't think of a way to write
|
||||||
|
* test code to hit them bug deterministically. The reason for the unpredictability is that
|
||||||
|
* the bugs depended on the {@link SharedFormulaRecord}s being searched in a particular order.
|
||||||
|
* At the time of writing of the test, the order was being determined by the call to {@link
|
||||||
|
* Collection#toArray(Object[])} on {@link HashMap#values()} where the items in the map were
|
||||||
|
* using default {@link Object#hashCode()}<br/>
|
||||||
|
*/
|
||||||
|
private static final int MAX_ATTEMPTS=5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bug happened when there were two or more shared formula ranges that overlapped. POI
|
||||||
|
* would sometimes associate formulas in the overlapping region with the wrong shared formula
|
||||||
|
*/
|
||||||
|
public void testPartiallyOverlappingRanges() throws IOException {
|
||||||
|
Record[] records;
|
||||||
|
|
||||||
|
int attempt=1;
|
||||||
|
do {
|
||||||
|
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(SAMPLE_FILE_NAME);
|
||||||
|
|
||||||
|
HSSFSheet sheet = wb.getSheetAt(0);
|
||||||
|
RecordInspector.getRecords(sheet, 0);
|
||||||
|
assertEquals("1+1", sheet.getRow(2).getCell(0).getCellFormula());
|
||||||
|
if ("1+1".equals(sheet.getRow(3).getCell(0).getCellFormula())) {
|
||||||
|
throw new AssertionFailedError("Identified bug - wrong shared formula record chosen"
|
||||||
|
+ " (attempt " + attempt + ")");
|
||||||
|
}
|
||||||
|
assertEquals("2+2", sheet.getRow(3).getCell(0).getCellFormula());
|
||||||
|
records = RecordInspector.getRecords(sheet, 0);
|
||||||
|
} while (attempt++ < MAX_ATTEMPTS);
|
||||||
|
|
||||||
|
int count=0;
|
||||||
|
for (int i = 0; i < records.length; i++) {
|
||||||
|
if (records[i] instanceof SharedFormulaRecord) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(2, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bug occurs for similar reasons to the bug in {@link #testPartiallyOverlappingRanges()}
|
||||||
|
* but the symptoms are much uglier - serialization fails with {@link NullPointerException}.<br/>
|
||||||
|
*/
|
||||||
|
public void testCompletelyOverlappedRanges() throws IOException {
|
||||||
|
Record[] records;
|
||||||
|
|
||||||
|
int attempt=1;
|
||||||
|
do {
|
||||||
|
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(SAMPLE_FILE_NAME);
|
||||||
|
|
||||||
|
HSSFSheet sheet = wb.getSheetAt(1);
|
||||||
|
try {
|
||||||
|
records = RecordInspector.getRecords(sheet, 0);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new AssertionFailedError("Identified bug " +
|
||||||
|
"- cannot reserialize completely overlapped shared formula"
|
||||||
|
+ " (attempt " + attempt + ")");
|
||||||
|
}
|
||||||
|
} while (attempt++ < MAX_ATTEMPTS);
|
||||||
|
|
||||||
|
int count=0;
|
||||||
|
for (int i = 0; i < records.length; i++) {
|
||||||
|
if (records[i] instanceof SharedFormulaRecord) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(2, count);
|
||||||
|
}
|
||||||
|
}
|
@ -60,6 +60,7 @@ public final class TestBugs extends TestCase {
|
|||||||
if (true) { // set to false to output test files
|
if (true) { // set to false to output test files
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
System.setProperty("poi.keep.tmp.files", "true");
|
||||||
File file;
|
File file;
|
||||||
try {
|
try {
|
||||||
file = TempFile.createTempFile(simpleFileName + "#", ".xls");
|
file = TempFile.createTempFile(simpleFileName + "#", ".xls");
|
||||||
|
Loading…
Reference in New Issue
Block a user