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:
parent
720b6bdf3d
commit
818880bf7f
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
src/java/org/apache/poi/hssf/record/formula/AreaI.java
Normal file
60
src/java/org/apache/poi/hssf/record/formula/AreaI.java
Normal 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();
|
||||||
|
}
|
@ -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() {
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user