diff --git a/legal/NOTICE b/legal/NOTICE index d5d7883b3..848c767bc 100644 --- a/legal/NOTICE +++ b/legal/NOTICE @@ -10,14 +10,6 @@ Common Public License Version 1.0: http://www.opensource.org/licenses/cpl.php See http://www.junit.org/ -A single data file of the POI component HDGF is based on VSDump, - and is under the GNU General Public Licence version 3 (GPL v3): - http://gplv3.fsf.org/ -Since this is a data file, and has no compiled version (the original - file is distributed in both source and binary versions), there should - be little difference in licencing requirements compared to the ASL. -See http://www.gnome.ru/projects/vsdump_en.html - The Office Open XML experimental support had additional dependencies, with their own licensing: diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 43b373d04..561ab0d4d 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -45,7 +45,13 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx - + + + + + 30978 - Fixed re-serialization of tRefErr3d and tAreaErr3d + 45234 - Removed incorrect shared formula conversion in CFRuleRecord + 45001 - Improved HWPF Range.replaceText() 44692 - Fixed HSSFPicture.resize() to properly resize pictures if the underlying columns/rows have modified size Support custom image renderers in HSLF Correctly increment the reference count of a blip when a picture is inserted diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 3ad07a216..ca47e0f20 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -42,7 +42,13 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx - + + + + + 30978 - Fixed re-serialization of tRefErr3d and tAreaErr3d + 45234 - Removed incorrect shared formula conversion in CFRuleRecord + 45001 - Improved HWPF Range.replaceText() 44692 - Fixed HSSFPicture.resize() to properly resize pictures if the underlying columns/rows have modified size Support custom image renderers in HSLF Correctly increment the reference count of a blip when a picture is inserted diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 5b38935f4..fa22cfb68 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -604,8 +604,28 @@ public class Workbook implements Model fixTabIdRecord(); } - // If we decide that we need to fix up - // NameRecords, do it here + // Within NameRecords, it's ok to have the formula + // part point at deleted sheets. It's also ok to + // have the ExternSheetNumber point at deleted + // sheets. + // However, the sheet index must be adjusted, or + // excel will break. (Sheet index is either 0 for + // global, or 1 based index to sheet) + int sheetNum1Based = sheetnum + 1; + for(int i=0; i sheetNum1Based) { + // Bump down by one, so still points + // at the same sheet + nr.setEqualsToIndexToSheet((short)( + nr.getEqualsToIndexToSheet()-1 + )); + } + } } /** diff --git a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java index 2b1705abe..d000b5311 100644 --- a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java +++ b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java @@ -14,14 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; -import java.util.Stack; - import org.apache.poi.hssf.model.FormulaParser; -import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.cf.BorderFormatting; import org.apache.poi.hssf.record.cf.FontFormatting; import org.apache.poi.hssf.record.cf.PatternFormatting; @@ -30,7 +26,6 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.StringUtil; /** * Conditional Formatting Rule Record. @@ -59,9 +54,6 @@ public final class CFRuleRecord extends Record private byte field_2_comparison_operator; - private short field_3_formula1_len; - private short field_4_formula2_len; - private int field_5_options; private static final BitField modificationBits = bf(0x003FFFFF); // Bits: font,align,bord,patt,prot @@ -121,8 +113,6 @@ public final class CFRuleRecord extends Record { field_1_condition_type=conditionType; field_2_comparison_operator=comparisonOperation; - field_3_formula1_len = (short)0; - field_4_formula2_len = (short)0; // Set modification flags to 1: by default options are not modified field_5_options = modificationBits.setValue(field_5_options, -1); @@ -147,8 +137,8 @@ public final class CFRuleRecord extends Record this(conditionType, comparisonOperation); field_1_condition_type = CONDITION_TYPE_CELL_VALUE_IS; field_2_comparison_operator = comparisonOperation; - setParsedExpression1(formula1); - setParsedExpression2(formula2); + field_17_formula1 = formula1; + field_18_formula2 = formula2; } /** @@ -167,63 +157,38 @@ public final class CFRuleRecord extends Record Ptg[] formula1 = parseFormula(formulaText1, workbook); Ptg[] formula2 = parseFormula(formulaText2, workbook); return new CFRuleRecord(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation, formula1, formula2); - } - /** - * Constructs a Formula record and sets its fields appropriately. - * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an - * "explanation of this bug in the documentation) or an exception - * will be throw upon validation - * - * @param in the RecordInputstream to read the record from - */ - - public CFRuleRecord(RecordInputStream in) - { + public CFRuleRecord(RecordInputStream in) { super(in); } - - protected void fillFields(RecordInputStream in) { - try { - field_1_condition_type = in.readByte(); - field_2_comparison_operator = in.readByte(); - field_3_formula1_len = in.readShort(); - field_4_formula2_len = in.readShort(); - field_5_options = in.readInt(); - field_6_not_used = in.readShort(); + field_1_condition_type = in.readByte(); + field_2_comparison_operator = in.readByte(); + int field_3_formula1_len = in.readUShort(); + int field_4_formula2_len = in.readUShort(); + field_5_options = in.readInt(); + field_6_not_used = in.readShort(); - if (containsFontFormattingBlock()) { - fontFormatting = new FontFormatting(in); - } - - if (containsBorderFormattingBlock()) { - borderFormatting = new BorderFormatting(in); - } - - if (containsPatternFormattingBlock()) { - patternFormatting = new PatternFormatting(in); - } - - if (field_3_formula1_len > 0) { - Stack ptgs = Ptg.createParsedExpressionTokens(field_3_formula1_len, in); - // Now convert any fields as required - ptgs = SharedFormulaRecord.convertSharedFormulas(ptgs, 0, 0); - field_17_formula1 = toArray(ptgs); - } - if (field_4_formula2_len > 0) { - Stack ptgs = Ptg.createParsedExpressionTokens(field_4_formula2_len, in); - - // Now convert any fields as required - ptgs = SharedFormulaRecord.convertSharedFormulas(ptgs, 0, 0); - field_18_formula2 = toArray(ptgs); - } - } catch (java.lang.UnsupportedOperationException uoe) { - throw new RecordFormatException(uoe); + if (containsFontFormattingBlock()) { + fontFormatting = new FontFormatting(in); } + if (containsBorderFormattingBlock()) { + borderFormatting = new BorderFormatting(in); + } + + if (containsPatternFormattingBlock()) { + patternFormatting = new PatternFormatting(in); + } + + if (field_3_formula1_len > 0) { + field_17_formula1 = Ptg.readTokens(field_3_formula1_len, in); + } + if (field_4_formula2_len > 0) { + field_18_formula2 = Ptg.readTokens(field_4_formula2_len, in); + } } public byte getConditionType() @@ -323,24 +288,6 @@ public final class CFRuleRecord extends Record } - /** - * get the length (in number of tokens) of the expression 1 - * @return expression length - */ - private short getExpression1Length() - { - return field_3_formula1_len; - } - - - /** - * get the length (in number of tokens) of the expression 2 - * @return expression length - */ - private short getExpression2Length() - { - return field_4_formula2_len; - } /** * get the option flags * @@ -489,16 +436,6 @@ public final class CFRuleRecord extends Record return field_18_formula2; } - private void setParsedExpression1(Ptg[] ptgs) { - short len = getTotalPtgSize(field_17_formula1 = ptgs); - field_3_formula1_len = len; - } - - private void setParsedExpression2(Ptg[] ptgs) { - short len = getTotalPtgSize(field_18_formula2 = ptgs); - field_4_formula2_len = len; - } - /** * called by constructor, should throw runtime exception in the event of a * record passed with a differing ID. @@ -519,6 +456,17 @@ public final class CFRuleRecord extends Record return sid; } + /** + * @param ptgs may be null + * @return encoded size of the formula + */ + private static int getFormulaSize(Ptg[] ptgs) { + if (ptgs == null) { + return 0; + } + return Ptg.getEncodedSize(ptgs); + } + /** * called by the class that is responsible for writing this sucker. * Subclasses should implement this so that their data is passed back in a @@ -528,18 +476,20 @@ public final class CFRuleRecord extends Record * @param data byte array containing instance data * @return number of bytes written */ - public int serialize(int pOffset, byte [] data) { + int formula1Len=getFormulaSize(field_17_formula1); + int formula2Len=getFormulaSize(field_18_formula2); + int offset = pOffset; int recordsize = getRecordSize(); LittleEndian.putShort(data, 0 + offset, sid); LittleEndian.putShort(data, 2 + offset, (short)(recordsize-4)); data[4 + offset] = field_1_condition_type; data[5 + offset] = field_2_comparison_operator; - LittleEndian.putShort(data, 6 + offset, field_3_formula1_len); - LittleEndian.putShort(data, 8 + offset, field_4_formula2_len); + LittleEndian.putUShort(data, 6 + offset, formula1Len); + LittleEndian.putUShort(data, 8 + offset, formula2Len); LittleEndian.putInt(data, 10 + offset, field_5_options); LittleEndian.putShort(data,14 + offset, field_6_not_used); @@ -562,16 +512,12 @@ public final class CFRuleRecord extends Record offset += patternFormatting.serialize(offset, data); } - if (getExpression1Length()>0) - { - Ptg.serializePtgStack(convertToTokenStack(field_17_formula1), data, offset); - offset += getExpression1Length(); + if (field_17_formula1 != null) { + offset += Ptg.serializePtgs(field_17_formula1, data, offset); } - if (getExpression2Length()>0) - { - Ptg.serializePtgStack(convertToTokenStack(field_18_formula2), data, offset); - offset += getExpression2Length(); + if (field_18_formula2 != null) { + offset += Ptg.serializePtgs(field_18_formula2, data, offset); } if(offset - pOffset != recordsize) { throw new IllegalStateException("write mismatch (" + (offset - pOffset) + "!=" + recordsize + ")"); @@ -586,24 +532,12 @@ public final class CFRuleRecord extends Record (containsFontFormattingBlock()?fontFormatting.getRawRecord().length:0)+ (containsBorderFormattingBlock()?8:0)+ (containsPatternFormattingBlock()?4:0)+ - getExpression1Length()+ - getExpression2Length() + getFormulaSize(field_17_formula1)+ + getFormulaSize(field_18_formula2) ; return retval; } - private short getTotalPtgSize(Ptg[] ptgs) - { - if( ptgs == null) { - return 0; - } - short retval = 0; - for (int i = 0; i < ptgs.length; i++) - { - retval += ptgs[i].getSize(); - } - return retval; - } public String toString() { @@ -629,8 +563,6 @@ public final class CFRuleRecord extends Record public Object clone() { CFRuleRecord rec = new CFRuleRecord(field_1_condition_type, field_2_comparison_operator); - rec.field_3_formula1_len = field_3_formula1_len; - rec.field_4_formula2_len = field_4_formula2_len; rec.field_5_options = field_5_options; rec.field_6_not_used = field_6_not_used; if (containsFontFormattingBlock()) { @@ -642,10 +574,10 @@ public final class CFRuleRecord extends Record if (containsPatternFormattingBlock()) { rec.patternFormatting = (PatternFormatting) patternFormatting.clone(); } - if (field_3_formula1_len > 0) { + if (field_17_formula1 != null) { rec.field_17_formula1 = (Ptg[]) field_17_formula1.clone(); } - if (field_4_formula2_len > 0) { + if (field_18_formula2 != null) { rec.field_18_formula2 = (Ptg[]) field_18_formula2.clone(); } @@ -653,30 +585,17 @@ public final class CFRuleRecord extends Record } /** + * 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 null if formula was null. */ - private static Ptg[] parseFormula(String formula, HSSFWorkbook workbook) - { + private static Ptg[] parseFormula(String formula, HSSFWorkbook workbook) { if(formula == null) { return null; } return FormulaParser.parse(formula, workbook); } - - // TODO - treat formulas as token arrays instead of Stacks throughout the rest of POI - private static Stack convertToTokenStack(Ptg[] ptgs) - { - Stack parsedExpression = new Stack(); - // fill the Ptg Stack with Ptgs of new formula - for (int k = 0; k < ptgs.length; k++) - { - parsedExpression.push(ptgs[ k ]); - } - return parsedExpression; - } - private static Ptg[] toArray(Stack ptgs) { - Ptg[] result = new Ptg[ptgs.size()]; - ptgs.toArray(result); - return result; - } } diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index 786c0b3d6..dbd796991 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; @@ -22,9 +21,8 @@ import java.util.Iterator; import java.util.List; import java.util.Stack; +import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.record.formula.Area3DPtg; -import org.apache.poi.hssf.record.formula.DeletedArea3DPtg; -import org.apache.poi.hssf.record.formula.DeletedRef3DPtg; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.UnionPtg; @@ -44,8 +42,7 @@ import org.apache.poi.util.StringUtil; * @author Glen Stampoultzis (glens at apache.org) * @version 1.0-pre */ - -public class NameRecord extends Record { +public final class NameRecord extends Record { /** */ public final static short sid = 0x18; //Docs says that it is 0x218 @@ -650,50 +647,9 @@ public class NameRecord extends Record { /** gets the reference , the area only (range) * @return area reference */ - public String getAreaReference(HSSFWorkbook book){ - if (field_13_name_definition == null || field_13_name_definition.isEmpty()) return "Error"; - Ptg ptg = (Ptg) field_13_name_definition.peek(); - String result = ""; - - // If it's a union, descend in and process - if (ptg.getClass() == UnionPtg.class) { - Iterator it =field_13_name_definition.iterator(); - while( it.hasNext() ) { - Ptg p = (Ptg)it.next(); - - String thisRes = getAreaRefString(p, book); - if(thisRes.length() > 0) { - // Add a comma to the end if needed - if(result.length() > 0 && !result.endsWith(",")) { - result += ","; - } - // And add the string it corresponds to - result += thisRes; - } - } - } else { - // Otherwise just get the string - result = getAreaRefString(ptg, book); - } - - return result; - } - - /** - * Turn the given ptg into a string, or - * return an empty string if nothing is possible - * for it. - */ - private String getAreaRefString(Ptg ptg,HSSFWorkbook book) { - if (ptg.getClass() == Area3DPtg.class){ - return ptg.toFormulaString(book); - } else if (ptg.getClass() == Ref3DPtg.class){ - return ptg.toFormulaString(book); - } else if (ptg.getClass() == DeletedArea3DPtg.class || ptg.getClass() == DeletedRef3DPtg.class) { - return "#REF!"; - } - return ""; - } + public String getAreaReference(HSSFWorkbook book){ + return FormulaParser.toFormulaString(book, field_13_name_definition); + } /** sets the reference , the area only (range) * @param ref area reference diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index 2804e37c3..8deaa919f 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -35,7 +35,7 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height (jheight at chariot dot net dot au) * @version 1.0-pre */ -public class Area3DPtg extends OperandPtg implements AreaI { +public final class Area3DPtg extends OperandPtg implements AreaI { public final static byte sid = 0x3b; private final static int SIZE = 11; // 10 + 1 for Ptg private short field_1_index_extern_sheet; diff --git a/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java index 36d7e1686..a1c5b3db5 100644 --- a/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java @@ -18,6 +18,9 @@ package org.apache.poi.hssf.record.formula; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.ss.usermodel.ErrorConstants; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.LittleEndian; /** * Title: Deleted Area 3D Ptg - 3D referecnce (Sheet + Area)

@@ -26,19 +29,30 @@ import org.apache.poi.hssf.record.RecordInputStream; * @author Patrick Luby * @version 1.0-pre */ - -public class DeletedArea3DPtg extends Area3DPtg -{ +public final class DeletedArea3DPtg extends OperandPtg { public final static byte sid = 0x3d; + private final int field_1_index_extern_sheet; + private final int unused1; + private final int unused2; - /** Creates new DeletedArea3DPtg */ - public DeletedArea3DPtg( String arearef, short externIdx ) - { - super(arearef, externIdx); - } - - public DeletedArea3DPtg( RecordInputStream in) - { - super(in); - } + public DeletedArea3DPtg( RecordInputStream in) { + field_1_index_extern_sheet = in.readUShort(); + unused1 = in.readInt(); + unused2 = in.readInt(); + } + public String toFormulaString(Workbook book) { + return ErrorConstants.getText(ErrorConstants.ERROR_REF); + } + public byte getDefaultOperandClass() { + return Ptg.CLASS_REF; + } + public int getSize() { + return 11; + } + public void writeBytes(byte[] data, int offset) { + LittleEndian.putByte(data, 0 + offset, sid + getPtgClass()); + LittleEndian.putUShort(data, 1 + offset, field_1_index_extern_sheet); + LittleEndian.putInt(data, 3 + offset, unused1); + LittleEndian.putInt(data, 7 + offset, unused2); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java index a3c780965..9312b2d76 100644 --- a/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java @@ -15,11 +15,13 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record.formula; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.ss.usermodel.ErrorConstants; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.LittleEndian; /** * Title: Deleted Reference 3D Ptg

@@ -28,16 +30,29 @@ import org.apache.poi.hssf.record.RecordInputStream; * @author Patrick Luby * @version 1.0-pre */ +public final class DeletedRef3DPtg extends OperandPtg { + public final static byte sid = 0x3c; + private final int field_1_index_extern_sheet; + private final int unused1; -public class DeletedRef3DPtg extends Ref3DPtg { - public final static byte sid = 0x3c; + /** Creates new DeletedRef3DPtg */ + public DeletedRef3DPtg(RecordInputStream in) { + field_1_index_extern_sheet = in.readUShort(); + unused1 = in.readInt(); + } - /** Creates new DeletedRef3DPtg */ - public DeletedRef3DPtg(RecordInputStream in) { - super(in); - } - - public DeletedRef3DPtg(String cellref, short externIdx ) { - super(cellref, externIdx); - } + public String toFormulaString(Workbook book) { + return ErrorConstants.getText(ErrorConstants.ERROR_REF); + } + public byte getDefaultOperandClass() { + return Ptg.CLASS_REF; + } + public int getSize() { + return 7; + } + public void writeBytes(byte[] data, int offset) { + LittleEndian.putByte(data, 0 + offset, sid + getPtgClass()); + LittleEndian.putUShort(data, 1 + offset, field_1_index_extern_sheet); + LittleEndian.putInt(data, 3 + offset, unused1); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index 0ec064581..52a5518e4 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -34,7 +34,7 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height (jheight at chariot dot net dot au) * @version 1.0-pre */ -public class Ref3DPtg extends OperandPtg { +public final class Ref3DPtg extends OperandPtg { public final static byte sid = 0x3a; private final static int SIZE = 7; // 6 + 1 for Ptg private short field_1_index_extern_sheet; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java b/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java index 89c25d1e8..f82ee721d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFErrorConstants.java @@ -16,67 +16,12 @@ ==================================================================== */ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ss.usermodel.ErrorConstants; /** * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6) * * @author Michael Harhen */ -public final class HSSFErrorConstants { - private HSSFErrorConstants() { - // no instances of this class - } - - /** #NULL! - Intersection of two cell ranges is empty */ - public static final int ERROR_NULL = 0x00; - /** #DIV/0! - Division by zero */ - public static final int ERROR_DIV_0 = 0x07; - /** #VALUE! - Wrong type of operand */ - public static final int ERROR_VALUE = 0x0F; - /** #REF! - Illegal or deleted cell reference */ - public static final int ERROR_REF = 0x17; - /** #NAME? - Wrong function or range name */ - public static final int ERROR_NAME = 0x1D; - /** #NUM! - Value range overflow */ - public static final int ERROR_NUM = 0x24; - /** #N/A - Argument or function not available */ - public static final int ERROR_NA = 0x2A; - - - /** - * @return Standard Excel error literal for the specified error code. - * @throws IllegalArgumentException if the specified error code is not one of the 7 - * standard error codes - */ - public static final String getText(int errorCode) { - switch(errorCode) { - case ERROR_NULL: return "#NULL!"; - case ERROR_DIV_0: return "#DIV/0!"; - case ERROR_VALUE: return "#VALUE!"; - case ERROR_REF: return "#REF!"; - case ERROR_NAME: return "#NAME?"; - case ERROR_NUM: return "#NUM!"; - case ERROR_NA: return "#N/A"; - } - throw new IllegalArgumentException("Bad error code (" + errorCode + ")"); - } - - /** - * @return true if the specified error code is a standard Excel error code. - */ - public static final boolean isValidCode(int errorCode) { - // This method exists because it would be bad to force clients to catch - // IllegalArgumentException if there were potential for passing an invalid error code. - switch(errorCode) { - case ERROR_NULL: - case ERROR_DIV_0: - case ERROR_VALUE: - case ERROR_REF: - case ERROR_NAME: - case ERROR_NUM: - case ERROR_NA: - return true; - } - return false; - } +public final class HSSFErrorConstants extends ErrorConstants { } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java index c042456a4..8e8cf40a1 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java @@ -39,7 +39,8 @@ public final class HSSFSheetConditionalFormatting { /** * A factory method allowing to create a conditional formatting rule - * with a cell comparison operator + * with a cell comparison operator

+ * TODO - formulas containing cell references are currently not parsed properly * * @param comparisonOperation - a constant value from * {@link HSSFConditionalFormattingRule.ComparisonOperator}:

@@ -72,8 +73,8 @@ public final class HSSFSheetConditionalFormatting { /** * A factory method allowing to create a conditional formatting rule with a formula.
* - * The formatting rules are applied by Excel when the value of the formula not equal to 0. - * + * The formatting rules are applied by Excel when the value of the formula not equal to 0.

+ * TODO - formulas containing cell references are currently not parsed properly * @param formula - formula for the valued, compared with the cell */ public HSSFConditionalFormattingRule createConditionalFormattingRule(String formula) { diff --git a/src/java/org/apache/poi/ss/usermodel/ErrorConstants.java b/src/java/org/apache/poi/ss/usermodel/ErrorConstants.java new file mode 100644 index 000000000..1bfa18452 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/ErrorConstants.java @@ -0,0 +1,82 @@ +/* ==================================================================== + 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.ss.usermodel; + +/** + * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6) + * + * @author Michael Harhen + */ +public class ErrorConstants { + protected ErrorConstants() { + // no instances of this class + } + + /** #NULL! - Intersection of two cell ranges is empty */ + public static final int ERROR_NULL = 0x00; + /** #DIV/0! - Division by zero */ + public static final int ERROR_DIV_0 = 0x07; + /** #VALUE! - Wrong type of operand */ + public static final int ERROR_VALUE = 0x0F; + /** #REF! - Illegal or deleted cell reference */ + public static final int ERROR_REF = 0x17; + /** #NAME? - Wrong function or range name */ + public static final int ERROR_NAME = 0x1D; + /** #NUM! - Value range overflow */ + public static final int ERROR_NUM = 0x24; + /** #N/A - Argument or function not available */ + public static final int ERROR_NA = 0x2A; + + + /** + * @return Standard Excel error literal for the specified error code. + * @throws IllegalArgumentException if the specified error code is not one of the 7 + * standard error codes + */ + public static final String getText(int errorCode) { + switch(errorCode) { + case ERROR_NULL: return "#NULL!"; + case ERROR_DIV_0: return "#DIV/0!"; + case ERROR_VALUE: return "#VALUE!"; + case ERROR_REF: return "#REF!"; + case ERROR_NAME: return "#NAME?"; + case ERROR_NUM: return "#NUM!"; + case ERROR_NA: return "#N/A"; + } + throw new IllegalArgumentException("Bad error code (" + errorCode + ")"); + } + + /** + * @return true if the specified error code is a standard Excel error code. + */ + public static final boolean isValidCode(int errorCode) { + // This method exists because it would be bad to force clients to catch + // IllegalArgumentException if there were potential for passing an invalid error code. + switch(errorCode) { + case ERROR_NULL: + case ERROR_DIV_0: + case ERROR_VALUE: + case ERROR_REF: + case ERROR_NAME: + case ERROR_NUM: + case ERROR_NA: + return true; + } + return false; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java index d1a84a5e4..44cb2b241 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java @@ -355,7 +355,7 @@ public abstract class TextShape extends SimpleShape { */ public float getMarginLeft(){ EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); - EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM); + EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__TEXTLEFT); int val = prop == null ? EMU_PER_INCH/10 : prop.getPropertyValue(); return (float)val/EMU_PER_POINT; } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java index 6324cd86a..80e9b7526 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java @@ -635,27 +635,24 @@ public class Range /** * Replace (one instance of) a piece of text with another... * - * @param pPlaceHolder The text to be replaced (e.g., "${company}") - * @param pValue The replacement text (e.g., "Cognocys, Inc.") - * @param pDocument The HWPFDocument in which the placeholder was found - * @param pStartOffset The offset or index where the CharacterRun begins - * @param pPlaceHolderIndex The offset or index of the placeholder, - * relative to the CharacterRun where - * pPlaceHolder was found + * @param pPlaceHolder The text to be replaced (e.g., "${organization}") + * @param pValue The replacement text (e.g., "Apache Software Foundation") + * @param pOffset The offset or index where the text to be replaced begins + * (relative to/within this Range) */ - protected void replaceText(String pPlaceHolder, String pValue, - int pStartOffset, int pPlaceHolderIndex, HWPFDocument pDocument) { - int absPlaceHolderIndex = pStartOffset + pPlaceHolderIndex; + public void replaceText(String pPlaceHolder, String pValue, int pOffset) + { + int absPlaceHolderIndex = getStartOffset() + pOffset; Range subRange = new Range( absPlaceHolderIndex, - (absPlaceHolderIndex + pPlaceHolder.length()), pDocument + (absPlaceHolderIndex + pPlaceHolder.length()), getDocument() ); if (subRange.usesUnicode()) { - absPlaceHolderIndex = pStartOffset + (pPlaceHolderIndex * 2); + absPlaceHolderIndex = getStartOffset() + (pOffset * 2); subRange = new Range( absPlaceHolderIndex, (absPlaceHolderIndex + (pPlaceHolder.length() * 2)), - pDocument + getDocument() ); } @@ -665,13 +662,13 @@ public class Range subRange = new Range( (absPlaceHolderIndex + pValue.length()), (absPlaceHolderIndex + pPlaceHolder.length() + pValue.length()), - pDocument + getDocument() ); if (subRange.usesUnicode()) subRange = new Range( (absPlaceHolderIndex + (pValue.length() * 2)), (absPlaceHolderIndex + (pPlaceHolder.length() * 2) + - (pValue.length() * 2)), pDocument + (pValue.length() * 2)), getDocument() ); subRange.delete(); @@ -942,4 +939,9 @@ public class Range return _end; } + + protected HWPFDocument getDocument() { + + return _doc; + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/text-margins.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/text-margins.ppt new file mode 100755 index 000000000..cf539aea7 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/text-margins.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestTextShape.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestTextShape.java index 25a8db918..7fc878756 100755 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestTextShape.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestTextShape.java @@ -23,6 +23,7 @@ import junit.framework.TestCase; import java.io.*; import java.util.ArrayList; +import java.util.HashMap; import org.apache.poi.hslf.usermodel.SlideShow; import org.apache.poi.hslf.record.TextHeaderAtom; @@ -157,4 +158,46 @@ public class TestTextShape extends TestCase { assertEquals("Testing TextShape", shape1.getTextRun().getText()); } + public void testMargins() throws IOException { + FileInputStream is = new FileInputStream(new File(cwd, "text-margins.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + Slide slide = ppt.getSlides()[0]; + + HashMap map = new HashMap(); + Shape[] shape = slide.getShapes(); + for (int i = 0; i < shape.length; i++) { + if(shape[i] instanceof TextShape){ + TextShape tx = (TextShape)shape[i]; + map.put(tx.getText(), tx); + } + } + + TextShape tx; + + tx = (TextShape)map.get("TEST1"); + assertEquals(0.1, tx.getMarginLeft()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.1, tx.getMarginRight()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.39, tx.getMarginTop()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginBottom()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + + tx = (TextShape)map.get("TEST2"); + assertEquals(0.1, tx.getMarginLeft()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.1, tx.getMarginRight()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginTop()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.39, tx.getMarginBottom()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + + tx = (TextShape)map.get("TEST3"); + assertEquals(0.39, tx.getMarginLeft()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.1, tx.getMarginRight()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginTop()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginBottom()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + + tx = (TextShape)map.get("TEST4"); + assertEquals(0.1, tx.getMarginLeft()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.39, tx.getMarginRight()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginTop()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + assertEquals(0.05, tx.getMarginBottom()*Shape.EMU_PER_POINT/Shape.EMU_PER_INCH, 0.01); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeReplacement.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeReplacement.doc new file mode 100644 index 000000000..949980d4e Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeReplacement.doc differ diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java new file mode 100644 index 000000000..4b2b9ce37 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java @@ -0,0 +1,119 @@ + +/* ==================================================================== + 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.hwpf.usermodel; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.util.List; + +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.model.PicturesTable; +import org.apache.poi.hwpf.usermodel.Picture; + +import junit.framework.TestCase; + +/** + * Test to see if Range.replaceText() works even if the Range contains a + * CharacterRun that uses Unicode characters. + */ +public class TestRangeReplacement extends TestCase { + + // u201c and u201d are "smart-quotes" + private String originalText = + "It is used to confirm that text replacement works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the ${organization} and all the POI contributors for their assistance in this matter.\r"; + private String searchText = "${organization}"; + private String replacementText = "Apache Software Foundation"; + private String expectedText = + "It is used to confirm that text replacement works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the Apache Software Foundation and all the POI contributors for their assistance in this matter.\r"; + + private String illustrativeDocFile; + + protected void setUp() throws Exception { + + String dirname = System.getProperty("HWPF.testdata.path"); + + illustrativeDocFile = dirname + "/testRangeReplacement.doc"; + } + + /** + * Test just opening the files + */ + public void testOpen() throws Exception { + + HWPFDocument docA = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + } + + /** + * Test (more "confirm" than test) that we have the general structure that we expect to have. + */ + public void testDocStructure() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + + assertEquals(1, range.numSections()); + Section section = range.getSection(0); + + assertEquals(5, section.numParagraphs()); + Paragraph para = section.getParagraph(2); + + assertEquals(5, para.numCharacterRuns()); + String text = para.getCharacterRun(0).text() + para.getCharacterRun(1).text() + + para.getCharacterRun(2).text() + para.getCharacterRun(3).text() + para.getCharacterRun(4).text(); + + assertEquals(originalText, text); + } + + /** + * Test that we can replace text in our Range with Unicode text. + */ + public void testRangeReplacement() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + assertEquals(1, range.numSections()); + + Section section = range.getSection(0); + assertEquals(5, section.numParagraphs()); + + Paragraph para = section.getParagraph(2); + + String text = para.text(); + assertEquals(originalText, text); + + int offset = text.indexOf(searchText); + assertEquals(181, offset); + + para.replaceText(searchText, replacementText, offset); + + // we need to let the model re-calculate the Range before we evaluate it + range = daDoc.getRange(); + + assertEquals(1, range.numSections()); + section = range.getSection(0); + + assertEquals(5, section.numParagraphs()); + para = section.getParagraph(2); + + text = para.text(); + assertEquals(expectedText, text); + } +} diff --git a/src/testcases/org/apache/poi/hssf/data/30978-alt.xls b/src/testcases/org/apache/poi/hssf/data/30978-alt.xls new file mode 100644 index 000000000..c59158288 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/30978-alt.xls differ diff --git a/src/testcases/org/apache/poi/hssf/record/TestCFRuleRecord.java b/src/testcases/org/apache/poi/hssf/record/TestCFRuleRecord.java index afc44e704..1eb052bec 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestCFRuleRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestCFRuleRecord.java @@ -17,12 +17,16 @@ package org.apache.poi.hssf.record; +import junit.framework.AssertionFailedError; import junit.framework.TestCase; 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; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.RefNPtg; +import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.util.LittleEndian; @@ -296,7 +300,57 @@ public final class TestCFRuleRecord extends TestCase // check all remaining flag bits (some are not well understood yet) assertEquals(0x203FFFFF, flags); } + + private static final byte[] DATA_REFN = { + // formula extracted from bugzilla 45234 att 22141 + 1, 3, + 9, // formula 1 length + 0, 0, 0, -1, -1, 63, 32, 2, -128, 0, 0, 0, 5, + // formula 1: "=B3=1" (formula is relative to B4) + 76, -1, -1, 0, -64, // tRefN(B1) + 30, 1, 0, + 11, + }; + /** + * tRefN and tAreaN tokens must be preserved when re-serializing conditional format formulas + */ + public void testReserializeRefNTokens() { + + RecordInputStream is = new TestcaseRecordInputStream(CFRuleRecord.sid, DATA_REFN); + CFRuleRecord rr = new CFRuleRecord(is); + Ptg[] ptgs = rr.getParsedExpression1(); + assertEquals(3, ptgs.length); + if (ptgs[0] instanceof RefPtg) { + throw new AssertionFailedError("Identified bug 45234"); + } + assertEquals(RefNPtg.class, ptgs[0].getClass()); + RefNPtg refNPtg = (RefNPtg) ptgs[0]; + assertTrue(refNPtg.isColRelative()); + assertTrue(refNPtg.isRowRelative()); + + byte[] data = rr.serialize(); + + if (!compareArrays(DATA_REFN, 0, data, 4, DATA_REFN.length)) { + fail("Did not re-serialize correctly"); + } + } + + private static boolean compareArrays(byte[] arrayA, int offsetA, byte[] arrayB, int offsetB, int length) { + + if (offsetA + length > arrayA.length) { + return false; + } + if (offsetB + length > arrayB.length) { + return false; + } + for (int i = 0; i < length; i++) { + if (arrayA[i+offsetA] != arrayB[i+offsetB]) { + return false; + } + } + return true; + } public static void main(String[] ignored_args) { diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 419bc33bb..79ef47be3 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -30,7 +30,10 @@ import junit.framework.TestCase; import org.apache.poi.ss.util.Region; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord; +import org.apache.poi.hssf.record.NameRecord; +import org.apache.poi.hssf.record.formula.DeletedArea3DPtg; import org.apache.poi.util.TempFile; /** @@ -990,4 +993,63 @@ public final class TestBugs extends TestCase { fail(); } catch(FileNotFoundException e) {} } + + /** + * Test that we can delete sheets without + * breaking the build in named ranges + * used for printing stuff. + * Currently broken, as we change the Ptg + */ + public void test30978() throws Exception { + HSSFWorkbook wb = openSample("30978-alt.xls"); + assertEquals(1, wb.getNumberOfNames()); + assertEquals(3, wb.getNumberOfSheets()); + + // Check all names fit within range, and use + // DeletedArea3DPtg + Workbook w = wb.getWorkbook(); + for(int i=0; i