Refactor some of the CFRuleRecord logic out to CFRuleBase, and begin work on CFRule12Record #58130

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1690527 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2015-07-12 20:38:57 +00:00
parent 5d31aa3a79
commit 192d798a0f
6 changed files with 365 additions and 123 deletions

View File

@ -0,0 +1,166 @@
/* ====================================================================
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;
import java.util.Arrays;
import org.apache.poi.hssf.record.common.FtrHeader;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.formula.Formula;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.util.LittleEndianOutput;
/**
* Conditional Formatting v12 Rule Record (0x087A).
*
* <p>This is for newer-style Excel conditional formattings,
* from Excel 2007 onwards.
*
* <p>{@link CFRuleRecord} is used where the condition type is
* {@link #CONDITION_TYPE_CELL_VALUE_IS} or {@link #CONDITION_TYPE_FORMULA},
* this is only used for the other types
*/
public final class CFRule12Record extends CFRuleBase {
public static final short sid = 0x087A;
private FtrHeader futureHeader;
private Formula formulaScale;
/** Creates new CFRuleRecord */
private CFRule12Record(byte conditionType, byte comparisonOperation) {
super(conditionType, comparisonOperation);
futureHeader = new FtrHeader();
futureHeader.setRecordType(sid);
// TODO Remaining fields
}
private CFRule12Record(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2, Ptg[] formulaScale) {
super(conditionType, comparisonOperation, formula1, formula2);
this.formulaScale = Formula.create(formulaScale);
// TODO Remaining fields
}
/**
* Creates a new comparison operation rule
*/
public static CFRule12Record create(HSSFSheet sheet, String formulaText) {
Ptg[] formula1 = parseFormula(formulaText, sheet);
return new CFRule12Record(CONDITION_TYPE_FORMULA, ComparisonOperator.NO_COMPARISON,
formula1, null, null);
}
/**
* Creates a new comparison operation rule
*/
public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation,
String formulaText1, String formulaText2) {
Ptg[] formula1 = parseFormula(formulaText1, sheet);
Ptg[] formula2 = parseFormula(formulaText2, sheet);
return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation,
formula1, formula2, null);
}
/**
* Creates a new comparison operation rule
*/
public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation,
String formulaText1, String formulaText2, String formulaTextScale) {
Ptg[] formula1 = parseFormula(formulaText1, sheet);
Ptg[] formula2 = parseFormula(formulaText2, sheet);
Ptg[] formula3 = parseFormula(formulaTextScale, sheet);
return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation,
formula1, formula2, formula3);
}
public CFRule12Record(RecordInputStream in) {
futureHeader = new FtrHeader(in);
setConditionType(in.readByte());
setComparisonOperation(in.readByte());
int field_3_formula1_len = in.readUShort();
int field_4_formula2_len = in.readUShort();
// TODO Handle the remainder
}
/**
* get the stack of the scale expression as a list
*
* @return list of tokens (casts stack to a list and returns it!)
* this method can return null is we are unable to create Ptgs from
* existing excel file
* callers should check for null!
*/
public Ptg[] getParsedExpressionScale() {
return formulaScale.getTokens();
}
public void setParsedExpressionScale(Ptg[] ptgs) {
formulaScale = Formula.create(ptgs);
}
public short getSid() {
return sid;
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param out the stream to write to
*/
public void serialize(LittleEndianOutput out) {
futureHeader.serialize(out);
int formula1Len=getFormulaSize(getFormula1());
int formula2Len=getFormulaSize(getFormula2());
out.writeByte(getConditionType());
out.writeByte(getComparisonOperation());
out.writeShort(formula1Len);
out.writeShort(formula2Len);
// TODO Output the rest
}
protected int getDataSize() {
// TODO Calculate
return 0;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[CFRULE12]\n");
buffer.append(" .condition_type =").append(getConditionType()).append("\n");
buffer.append(" TODO The rest!\n");
buffer.append(" Formula 1 =").append(Arrays.toString(getFormula1().getTokens())).append("\n");
buffer.append(" Formula 2 =").append(Arrays.toString(getFormula2().getTokens())).append("\n");
buffer.append(" Formula S =").append(Arrays.toString(formulaScale.getTokens())).append("\n");
buffer.append("[/CFRULE12]\n");
return buffer.toString();
}
public Object clone() {
CFRule12Record rec = new CFRule12Record(getConditionType(), getComparisonOperation());
// TODO The other fields
rec.setFormula1(getFormula1().copy());
rec.setFormula2(getFormula2().copy());
rec.formulaScale = formulaScale.copy();
return rec;
}
}

View File

@ -0,0 +1,165 @@
/* ====================================================================
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;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.formula.Formula;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.Ptg;
/**
* Conditional Formatting Rules. This can hold old-style rules
*
*
* <p>This is for the older-style Excel conditional formattings,
* new-style (Excel 2007+) also make use of {@link CFRule12Record}
* and {@link CFExRuleRecord} for their rules.
*/
public abstract class CFRuleBase extends StandardRecord {
public static final class ComparisonOperator {
public static final byte NO_COMPARISON = 0;
public static final byte BETWEEN = 1;
public static final byte NOT_BETWEEN = 2;
public static final byte EQUAL = 3;
public static final byte NOT_EQUAL = 4;
public static final byte GT = 5;
public static final byte LT = 6;
public static final byte GE = 7;
public static final byte LE = 8;
private static final byte max_operator = 8;
}
private byte condition_type;
// The only kinds that CFRuleRecord handles
public static final byte CONDITION_TYPE_CELL_VALUE_IS = 1;
public static final byte CONDITION_TYPE_FORMULA = 2;
// These are CFRule12Rule only
public static final byte CONDITION_TYPE_COLOR_SCALE = 3;
public static final byte CONDITION_TYPE_DATA_BAR = 4;
public static final byte CONDITION_TYPE_FILTER = 5;
public static final byte CONDITION_TYPE_ICON_SET = 6;
private byte comparison_operator;
private Formula formula1;
private Formula formula2;
/** Creates new CFRuleRecord */
protected CFRuleBase(byte conditionType, byte comparisonOperation) {
setConditionType(conditionType);
setComparisonOperation(comparisonOperation);
formula1 = Formula.create(Ptg.EMPTY_PTG_ARRAY);
formula2 = Formula.create(Ptg.EMPTY_PTG_ARRAY);
}
protected CFRuleBase(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2) {
this(conditionType, comparisonOperation);
this.formula1 = Formula.create(formula1);
this.formula2 = Formula.create(formula2);
}
protected CFRuleBase() {}
public byte getConditionType() {
return condition_type;
}
protected void setConditionType(byte condition_type) {
if ((this instanceof CFRuleRecord)) {
if (condition_type == CONDITION_TYPE_CELL_VALUE_IS ||
condition_type == CONDITION_TYPE_FORMULA) {
// Good, valid combination
} else {
throw new IllegalArgumentException("CFRuleRecord only accepts Value-Is and Formula types");
}
}
this.condition_type = condition_type;
}
public void setComparisonOperation(byte operation) {
if (operation < 0 || operation > ComparisonOperator.max_operator)
throw new IllegalArgumentException(
"Valid operators are only in the range 0 to " +ComparisonOperator.max_operator);
this.comparison_operator = operation;
}
public byte getComparisonOperation() {
return comparison_operator;
}
/**
* get the stack of the 1st expression as a list
*
* @return list of tokens (casts stack to a list and returns it!)
* this method can return null is we are unable to create Ptgs from
* existing excel file
* callers should check for null!
*/
public Ptg[] getParsedExpression1() {
return formula1.getTokens();
}
public void setParsedExpression1(Ptg[] ptgs) {
formula1 = Formula.create(ptgs);
}
protected Formula getFormula1() {
return formula1;
}
protected void setFormula1(Formula formula1) {
this.formula1 = formula1;
}
/**
* get the stack of the 2nd expression as a list
*
* @return array of {@link Ptg}s, possibly <code>null</code>
*/
public Ptg[] getParsedExpression2() {
return Formula.getTokens(formula2);
}
public void setParsedExpression2(Ptg[] ptgs) {
formula2 = Formula.create(ptgs);
}
protected Formula getFormula2() {
return formula2;
}
protected void setFormula2(Formula formula2) {
this.formula2 = formula2;
}
/**
* @param ptgs must not be <code>null</code>
* @return encoded size of the formula tokens (does not include 2 bytes for ushort length)
*/
protected static int getFormulaSize(Formula formula) {
return formula.getEncodedTokenSize();
}
/**
* TODO - parse conditional format formulas properly i.e. produce tRefN and tAreaN instead of tRef and tArea
* this call will produce the wrong results if the formula contains any cell references
* One approach might be to apply the inverse of SharedFormulaRecord.convertSharedFormulas(Stack, int, int)
* Note - two extra parameters (rowIx & colIx) will be required. They probably come from one of the Region objects.
*
* @return <code>null</code> if <tt>formula</tt> was null.
*/
protected static Ptg[] parseFormula(String formula, HSSFSheet sheet) {
if(formula == null) {
return null;
}
int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet);
return HSSFFormulaParser.parse(formula, sheet.getWorkbook(), FormulaType.CELL, sheetIndex);
}
}

View File

@ -19,13 +19,11 @@ package org.apache.poi.hssf.record;
import java.util.Arrays;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.cf.BorderFormatting;
import org.apache.poi.hssf.record.cf.FontFormatting;
import org.apache.poi.hssf.record.cf.PatternFormatting;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.formula.Formula;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
@ -38,28 +36,10 @@ import org.apache.poi.util.LittleEndianOutput;
* new-style (Excel 2007+) also make use of {@link CFRule12Record}
* and {@link CFExRuleRecord} for their rules.
*/
public final class CFRuleRecord extends StandardRecord {
public final class CFRuleRecord extends CFRuleBase {
public static final short sid = 0x01B1;
public static final class ComparisonOperator {
public static final byte NO_COMPARISON = 0;
public static final byte BETWEEN = 1;
public static final byte NOT_BETWEEN = 2;
public static final byte EQUAL = 3;
public static final byte NOT_EQUAL = 4;
public static final byte GT = 5;
public static final byte LT = 6;
public static final byte GE = 7;
public static final byte LE = 8;
}
private byte field_1_condition_type;
public static final byte CONDITION_TYPE_CELL_VALUE_IS = 1;
public static final byte CONDITION_TYPE_FORMULA = 2;
private byte field_2_comparison_operator;
private int field_5_options;
private int field_5_options;
private static final BitField modificationBits = bf(0x003FFFFF); // Bits: font,align,bord,patt,prot
private static final BitField alignHor = bf(0x00000001); // 0 = Horizontal alignment modified
@ -104,15 +84,17 @@ public final class CFRuleRecord extends StandardRecord {
private PatternFormatting _patternFormatting;
private Formula field_17_formula1;
private Formula field_18_formula2;
/** Creates new CFRuleRecord */
private CFRuleRecord(byte conditionType, byte comparisonOperation)
{
field_1_condition_type=conditionType;
field_2_comparison_operator=comparisonOperation;
private CFRuleRecord(byte conditionType, byte comparisonOperation) {
super(conditionType, comparisonOperation);
setDefaults();
}
private CFRuleRecord(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2) {
super(conditionType, comparisonOperation, formula1, formula2);
setDefaults();
}
private void setDefaults() {
// Set modification flags to 1: by default options are not modified
field_5_options = modificationBits.setValue(field_5_options, -1);
// Set formatting block flags to 0 (no formatting blocks)
@ -123,14 +105,6 @@ public final class CFRuleRecord extends StandardRecord {
_fontFormatting=null;
_borderFormatting=null;
_patternFormatting=null;
field_17_formula1=Formula.create(Ptg.EMPTY_PTG_ARRAY);
field_18_formula2=Formula.create(Ptg.EMPTY_PTG_ARRAY);
}
private CFRuleRecord(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2) {
this(conditionType, comparisonOperation);
field_17_formula1 = Formula.create(formula1);
field_18_formula2 = Formula.create(formula2);
}
/**
@ -152,8 +126,8 @@ public final class CFRuleRecord extends StandardRecord {
}
public CFRuleRecord(RecordInputStream in) {
field_1_condition_type = in.readByte();
field_2_comparison_operator = in.readByte();
setConditionType(in.readByte());
setComparisonOperation(in.readByte());
int field_3_formula1_len = in.readUShort();
int field_4_formula2_len = in.readUShort();
field_5_options = in.readInt();
@ -172,12 +146,8 @@ public final class CFRuleRecord extends StandardRecord {
}
// "You may not use unions, intersections or array constants in Conditional Formatting criteria"
field_17_formula1 = Formula.read(field_3_formula1_len, in);
field_18_formula2 = Formula.read(field_4_formula2_len, in);
}
public byte getConditionType() {
return field_1_condition_type;
setFormula1(Formula.read(field_3_formula1_len, in));
setFormula2(Formula.read(field_4_formula2_len, in));
}
public boolean containsFontFormattingBlock() {
@ -237,13 +207,6 @@ public final class CFRuleRecord extends StandardRecord {
setOptionFlag(false,prot);
}
public void setComparisonOperation(byte operation) {
field_2_comparison_operator = operation;
}
public byte getComparisonOperation() {
return field_2_comparison_operator;
}
/**
* get the option flags
*
@ -331,45 +294,10 @@ public final class CFRuleRecord extends StandardRecord {
field_5_options = field.setBoolean(field_5_options, flag);
}
/**
* get the stack of the 1st expression as a list
*
* @return list of tokens (casts stack to a list and returns it!)
* this method can return null is we are unable to create Ptgs from
* existing excel file
* callers should check for null!
*/
public Ptg[] getParsedExpression1() {
return field_17_formula1.getTokens();
}
public void setParsedExpression1(Ptg[] ptgs) {
field_17_formula1 = Formula.create(ptgs);
}
/**
* get the stack of the 2nd expression as a list
*
* @return array of {@link Ptg}s, possibly <code>null</code>
*/
public Ptg[] getParsedExpression2() {
return Formula.getTokens(field_18_formula2);
}
public void setParsedExpression2(Ptg[] ptgs) {
field_18_formula2 = Formula.create(ptgs);
}
public short getSid() {
return sid;
}
/**
* @param ptgs must not be <code>null</code>
* @return encoded size of the formula tokens (does not include 2 bytes for ushort length)
*/
private static int getFormulaSize(Formula formula) {
return formula.getEncodedTokenSize();
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
@ -378,11 +306,11 @@ public final class CFRuleRecord extends StandardRecord {
* @param out the stream to write to
*/
public void serialize(LittleEndianOutput out) {
int formula1Len=getFormulaSize(field_17_formula1);
int formula2Len=getFormulaSize(field_18_formula2);
int formula1Len=getFormulaSize(getFormula1());
int formula2Len=getFormulaSize(getFormula2());
out.writeByte(field_1_condition_type);
out.writeByte(field_2_comparison_operator);
out.writeByte(getConditionType());
out.writeByte(getComparisonOperation());
out.writeShort(formula1Len);
out.writeShort(formula2Len);
out.writeInt(field_5_options);
@ -401,8 +329,8 @@ public final class CFRuleRecord extends StandardRecord {
_patternFormatting.serialize(out);
}
field_17_formula1.serializeTokens(out);
field_18_formula2.serializeTokens(out);
getFormula1().serializeTokens(out);
getFormula2().serializeTokens(out);
}
protected int getDataSize() {
@ -410,15 +338,15 @@ public final class CFRuleRecord extends StandardRecord {
(containsFontFormattingBlock()?_fontFormatting.getRawRecord().length:0)+
(containsBorderFormattingBlock()?8:0)+
(containsPatternFormattingBlock()?4:0)+
getFormulaSize(field_17_formula1)+
getFormulaSize(field_18_formula2);
getFormulaSize(getFormula1())+
getFormulaSize(getFormula2());
return i;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[CFRULE]\n");
buffer.append(" .condition_type =").append(field_1_condition_type).append("\n");
buffer.append(" .condition_type =").append(getConditionType()).append("\n");
buffer.append(" OPTION FLAGS=0x").append(Integer.toHexString(getOptions())).append("\n");
if (containsFontFormattingBlock()) {
buffer.append(_fontFormatting.toString()).append("\n");
@ -429,14 +357,14 @@ public final class CFRuleRecord extends StandardRecord {
if (containsPatternFormattingBlock()) {
buffer.append(_patternFormatting.toString()).append("\n");
}
buffer.append(" Formula 1 =").append(Arrays.toString(field_17_formula1.getTokens())).append("\n");
buffer.append(" Formula 2 =").append(Arrays.toString(field_18_formula2.getTokens())).append("\n");
buffer.append(" Formula 1 =").append(Arrays.toString(getFormula1().getTokens())).append("\n");
buffer.append(" Formula 2 =").append(Arrays.toString(getFormula2().getTokens())).append("\n");
buffer.append("[/CFRULE]\n");
return buffer.toString();
}
public Object clone() {
CFRuleRecord rec = new CFRuleRecord(field_1_condition_type, field_2_comparison_operator);
CFRuleRecord rec = new CFRuleRecord(getConditionType(), getComparisonOperation());
rec.field_5_options = field_5_options;
rec.field_6_not_used = field_6_not_used;
if (containsFontFormattingBlock()) {
@ -448,25 +376,9 @@ public final class CFRuleRecord extends StandardRecord {
if (containsPatternFormattingBlock()) {
rec._patternFormatting = (PatternFormatting) _patternFormatting.clone();
}
rec.field_17_formula1 = field_17_formula1.copy();
rec.field_18_formula2 = field_18_formula2.copy();
rec.setFormula1(getFormula1().copy());
rec.setFormula2(getFormula2().copy());
return rec;
}
/**
* TODO - parse conditional format formulas properly i.e. produce tRefN and tAreaN instead of tRef and tArea
* this call will produce the wrong results if the formula contains any cell references
* One approach might be to apply the inverse of SharedFormulaRecord.convertSharedFormulas(Stack, int, int)
* Note - two extra parameters (rowIx & colIx) will be required. They probably come from one of the Region objects.
*
* @return <code>null</code> if <tt>formula</tt> was null.
*/
private static Ptg[] parseFormula(String formula, HSSFSheet sheet) {
if(formula == null) {
return null;
}
int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet);
return HSSFFormulaParser.parse(formula, sheet.getWorkbook(), FormulaType.CELL, sheetIndex);
}
}

View File

@ -18,8 +18,8 @@
package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.CFRuleBase.ComparisonOperator;
import org.apache.poi.hssf.record.CFRuleRecord;
import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator;
import org.apache.poi.hssf.record.cf.BorderFormatting;
import org.apache.poi.hssf.record.cf.FontFormatting;
import org.apache.poi.hssf.record.cf.PatternFormatting;

View File

@ -21,7 +21,7 @@ import static org.junit.Assert.assertArrayEquals;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator;
import org.apache.poi.hssf.record.CFRuleBase.ComparisonOperator;
import org.apache.poi.hssf.record.cf.BorderFormatting;
import org.apache.poi.hssf.record.cf.FontFormatting;
import org.apache.poi.hssf.record.cf.PatternFormatting;
@ -36,8 +36,7 @@ import org.apache.poi.util.LittleEndian;
/**
* Tests the serialization and deserialization of the TestCFRuleRecord
* class works correctly.
*
* @author Dmitriy Kumshayev
* TODO Add {@link CFRule12Record} tests
*/
public final class TestCFRuleRecord extends TestCase {
public void testConstructors () {

View File

@ -27,11 +27,11 @@ import junit.framework.TestCase;
import org.apache.poi.hssf.model.RecordStream;
import org.apache.poi.hssf.record.CFHeaderRecord;
import org.apache.poi.hssf.record.CFRuleBase.ComparisonOperator;
import org.apache.poi.hssf.record.CFRuleRecord;
import org.apache.poi.hssf.record.RecordFactory;
import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.LittleEndian;