Further support for whole-column references, including formula strings and the evaluator. Also has some new tests for it

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@628065 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-02-15 13:50:38 +00:00
parent 720b6bdf3d
commit 818880bf7f
9 changed files with 154 additions and 33 deletions

View File

@ -36,7 +36,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44410 - Partial support for whole-column ranges, such as C:C, in the formula evaluator</action> <action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action> <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action> <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
<action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action> <action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>

View File

@ -33,7 +33,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44410 - Partial support for whole-column ranges, such as C:C, in the formula evaluator</action> <action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action> <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action> <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
<action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action> <action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>

View File

@ -17,15 +17,13 @@
package org.apache.poi.hssf.record.formula; package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.SheetReferences;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.util.BitField; import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
/** /**
@ -38,7 +36,7 @@ import org.apache.poi.util.BitFieldFactory;
* @version 1.0-pre * @version 1.0-pre
*/ */
public class Area3DPtg extends Ptg public class Area3DPtg extends Ptg implements AreaI
{ {
public final static byte sid = 0x3b; public final static byte sid = 0x3b;
private final static int SIZE = 11; // 10 + 1 for Ptg private final static int SIZE = 11; // 10 + 1 for Ptg
@ -263,15 +261,18 @@ public class Area3DPtg extends Ptg
*/ */
public String toFormulaString(Workbook book) public String toFormulaString(Workbook book)
{ {
// First do the sheet name
StringBuffer retval = new StringBuffer(); StringBuffer retval = new StringBuffer();
String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet); String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet);
if(sheetName != null) { if(sheetName != null) {
SheetNameFormatter.appendFormat(retval, sheetName); SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' ); retval.append( '!' );
} }
retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).formatAsString() );
retval.append( ':' ); // Now the normal area bit
retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).formatAsString() ); retval.append( AreaPtg.toFormulaString(this, book) );
// All done
return retval.toString(); return retval.toString();
} }

View File

@ -0,0 +1,60 @@
/* ====================================================================
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.formula;
/**
* Common interface for AreaPtg and Area3DPtg, and their
* child classes.
*/
public interface AreaI {
/**
* @return the first row in the area
*/
public short getFirstRow();
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
public short getLastRow();
/**
* @return the first column number in the area.
*/
public short getFirstColumn();
/**
* @return lastcolumn in the area
*/
public short getLastColumn();
/**
* @return isrelative first column to relative or not
*/
public boolean isFirstColRelative();
/**
* @return lastcol relative or not
*/
public boolean isLastColRelative();
/**
* @return whether or not the first row is a relative reference or not.
*/
public boolean isFirstRowRelative();
/**
* @return last row relative or not
*/
public boolean isLastRowRelative();
}

View File

@ -34,7 +34,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
*/ */
public class AreaPtg public class AreaPtg
extends Ptg extends Ptg implements AreaI
{ {
public final static short sid = 0x25; public final static short sid = 0x25;
private final static int SIZE = 9; private final static int SIZE = 9;
@ -284,11 +284,17 @@ public class AreaPtg
public String toFormulaString(Workbook book) public String toFormulaString(Workbook book)
{ {
// TODO: return toFormulaString(this, book);
// For a reference like C:C, which is stored as }
// C1:C0 (last row is -1), return as C:C protected static String toFormulaString(AreaI area, Workbook book) {
return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).formatAsString() + ":" + CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative());
(new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).formatAsString(); CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative());
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
return (new AreaReference(topLeft, botRight)).formatAsString();
} else {
return topLeft.formatAsString() + ":" + botRight.formatAsString();
}
} }
public byte getDefaultOperandClass() { public byte getDefaultOperandClass() {

View File

@ -47,6 +47,17 @@ public final class AreaReference {
} }
String[] parts = separateAreaRefs(reference); String[] parts = separateAreaRefs(reference);
// Special handling for whole-column references
if(parts.length == 2 && parts[0].length() == 1 &&
parts[1].length() == 1 &&
parts[0].charAt(0) >= 'A' && parts[0].charAt(0) <= 'Z' &&
parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') {
// Represented internally as x$1 to x$0
parts[0] = parts[0] + "$1";
parts[1] = parts[1] + "$0";
}
_firstCell = new CellReference(parts[0]); _firstCell = new CellReference(parts[0]);
if(parts.length == 2) { if(parts.length == 2) {
@ -58,6 +69,15 @@ public final class AreaReference {
} }
} }
/**
* Creates an area ref from a pair of Cell References.
*/
public AreaReference(CellReference topLeft, CellReference botRight) {
_firstCell = topLeft;
_lastCell = botRight;
_isSingleCell = false;
}
/** /**
* Is the reference for a contiguous (i.e. * Is the reference for a contiguous (i.e.
* unbroken) area, or is it made up of * unbroken) area, or is it made up of
@ -72,6 +92,24 @@ public final class AreaReference {
return false; return false;
} }
/**
* Is the reference for a whole-column reference,
* such as C:C or D:G ?
*/
public static boolean isWholeColumnReference(CellReference topLeft, CellReference botRight) {
// These are represented as something like
// C$1:C$0 or D$1:F$0
// i.e. absolute from 1st row to 0th one
if(topLeft.getRow() == 0 && topLeft.isRowAbsolute() &&
botRight.getRow() == -1 && botRight.isRowAbsolute()) {
return true;
}
return false;
}
public boolean isWholeColumnReference() {
return isWholeColumnReference(_firstCell, _lastCell);
}
/** /**
* Takes a non-contiguous area reference, and * Takes a non-contiguous area reference, and
* returns an array of contiguous area references. * returns an array of contiguous area references.
@ -150,6 +188,14 @@ public final class AreaReference {
* @return the text representation of this area reference as it would appear in a formula. * @return the text representation of this area reference as it would appear in a formula.
*/ */
public String formatAsString() { public String formatAsString() {
// Special handling for whole-column references
if(isWholeColumnReference()) {
return
CellReference.convertNumToColString(_firstCell.getCol())
+ ":" +
CellReference.convertNumToColString(_lastCell.getCol());
}
StringBuffer sb = new StringBuffer(32); StringBuffer sb = new StringBuffer(32);
sb.append(_firstCell.formatAsString()); sb.append(_firstCell.formatAsString());
if(!_isSingleCell) { if(!_isSingleCell) {

View File

@ -188,9 +188,11 @@ public final class CellReference {
} }
/** /**
* takes in a 0-based base-10 column and returns a ALPHA-26 representation * Takes in a 0-based base-10 column and returns a ALPHA-26
* representation.
* eg column #3 -> D
*/ */
private static String convertNumToColString(int col) { protected static String convertNumToColString(int col) {
String retval = null; String retval = null;
int mod = col % 26; int mod = col % 26;
int div = col / 26; int div = col / 26;

View File

@ -23,11 +23,9 @@ import java.io.FileInputStream;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.functions.Sumproduct;
/** /**
* Bug 44410: SUM(C:C) is valid in excel, and means a sum * Bug 44410: SUM(C:C) is valid in excel, and means a sum
@ -52,6 +50,8 @@ public class TestBug44410 extends TestCase {
HSSFRow rowIDX = (HSSFRow)sheet.getRow(3); HSSFRow rowIDX = (HSSFRow)sheet.getRow(3);
// =sum(C:C) -> 6 // =sum(C:C) -> 6
HSSFRow rowSUM = (HSSFRow)sheet.getRow(4); HSSFRow rowSUM = (HSSFRow)sheet.getRow(4);
// =sum(C:D) -> 66
HSSFRow rowSUM2D = (HSSFRow)sheet.getRow(5);
// Test the sum // Test the sum
HSSFCell cellSUM = rowSUM.getCell((short)0); HSSFCell cellSUM = rowSUM.getCell((short)0);
@ -59,8 +59,9 @@ public class TestBug44410 extends TestCase {
FormulaRecordAggregate frec = FormulaRecordAggregate frec =
(FormulaRecordAggregate)cellSUM.getCellValueRecord(); (FormulaRecordAggregate)cellSUM.getCellValueRecord();
List ops = frec.getFormulaRecord().getParsedExpression(); List ops = frec.getFormulaRecord().getParsedExpression();
assertEquals(2, ops.size());
assertEquals(AreaPtg.class, ops.get(0).getClass()); assertEquals(AreaPtg.class, ops.get(0).getClass());
assertEquals(AttrPtg.class, ops.get(1).getClass()); assertEquals(FuncVarPtg.class, ops.get(1).getClass());
// Actually stored as C1 to C0 (last row is -1) // Actually stored as C1 to C0 (last row is -1)
AreaPtg ptg = (AreaPtg)ops.get(0); AreaPtg ptg = (AreaPtg)ops.get(0);
@ -68,12 +69,12 @@ public class TestBug44410 extends TestCase {
assertEquals(2, ptg.getLastColumn()); assertEquals(2, ptg.getLastColumn());
assertEquals(0, ptg.getFirstRow()); assertEquals(0, ptg.getFirstRow());
assertEquals(-1, ptg.getLastRow()); assertEquals(-1, ptg.getLastRow());
assertEquals("C$1:C$0", ptg.toFormulaString(wb.getWorkbook())); assertEquals("C:C", ptg.toFormulaString(wb.getWorkbook()));
// So will show up wrong here, as we don't // Will show as C:C, but won't know how many
// have the sheet to hand when turning the Ptgs // rows it covers as we don't have the sheet
// into a string // to hand when turning the Ptgs into a string
assertEquals("SUM(C$1:C$0)", cellSUM.getCellFormula()); assertEquals("SUM(C:C)", cellSUM.getCellFormula());
eva.setCurrentRow(rowSUM); eva.setCurrentRow(rowSUM);
// But the evaluator knows the sheet, so it // But the evaluator knows the sheet, so it
@ -82,12 +83,17 @@ public class TestBug44410 extends TestCase {
// Test the index // Test the index
// Again, the formula string will be wrong, as we // Again, the formula string will be right but
// don't have the sheet to hand, but the // lacking row count, evaluated will be right
// evaluator will be correct
HSSFCell cellIDX = rowIDX.getCell((short)0); HSSFCell cellIDX = rowIDX.getCell((short)0);
assertEquals("INDEX(C$1:C$0,2,1)", cellIDX.getCellFormula()); assertEquals("INDEX(C:C,2,1)", cellIDX.getCellFormula());
eva.setCurrentRow(rowIDX); eva.setCurrentRow(rowIDX);
assertEquals(2, eva.evaluate(cellIDX).getNumberValue(), 0); assertEquals(2, eva.evaluate(cellIDX).getNumberValue(), 0);
// Across two colums
HSSFCell cellSUM2D = rowSUM2D.getCell((short)0);
assertEquals("SUM(C:D)", cellSUM2D.getCellFormula());
eva.setCurrentRow(rowSUM2D);
assertEquals(66, eva.evaluate(cellSUM2D).getNumberValue(), 0);
} }
} }