From d7da425d581e354aa82e688a5d34da5793d6349b Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Mon, 3 Dec 2007 13:17:41 +0000 Subject: [PATCH] More code from bug #27511, now ported to the new style record code git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@600519 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hssf/record/DVRecord.java | 590 ++++++++++++++++++ .../apache/poi/hssf/record/RecordFactory.java | 3 +- .../poi/hssf/util/HSSFCellRangeAddress.java | 23 +- .../hssf/data/templateExcelWithAutofilter.xls | Bin 0 -> 13824 bytes 4 files changed, 603 insertions(+), 13 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/record/DVRecord.java create mode 100644 src/testcases/org/apache/poi/hssf/data/templateExcelWithAutofilter.xls diff --git a/src/java/org/apache/poi/hssf/record/DVRecord.java b/src/java/org/apache/poi/hssf/record/DVRecord.java new file mode 100644 index 0000000000..0bae009bd3 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/DVRecord.java @@ -0,0 +1,590 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.util.BitField; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; +import org.apache.poi.hssf.util.HSSFCellRangeAddress; +import org.apache.poi.hssf.record.formula.Ptg; + +import java.io.IOException; +import java.util.Stack; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * Title: DV Record

+ * Description: This record stores data validation settings and a list of cell ranges + * which contain these settings. The data validation settings of a sheet + * are stored in a sequential list of DV records. This list is followed by + * DVAL record(s) + * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) + * @version 2.0-pre + */ +public class DVRecord extends Record +{ + public final static short sid = 0x01BE; + + /** + * Option flags + */ + private int field_option_flags; + + /** + * Title of the prompt box + */ + private String field_title_prompt; + + /** + * Title of the error box + */ + private String field_title_error; + + /** + * Text of the prompt box + */ + private String field_text_prompt; + + /** + * Text of the error box + */ + private String field_text_error; + + /** + * Size of the formula data for first condition + */ + private short field_size_first_formula; + + /** + * Not used + */ + private short field_not_used_1 = 0x3FE0; + + /** + * Formula data for first condition (RPN token array without size field) + */ + private Stack field_rpn_token_1 ; + + /** + * Size of the formula data for second condition + */ + private short field_size_sec_formula; + + /** + * Not used + */ + private short field_not_used_2 = 0x0000; + + /** + * Formula data for second condition (RPN token array without size field) + */ + private Stack field_rpn_token_2 ; + + /** + * Cell range address list with all affected ranges + */ + private HSSFCellRangeAddress field_regions; + + public static final Integer STRING_PROMPT_TITLE = new Integer(0); + public static final Integer STRING_ERROR_TITLE = new Integer(1); + public static final Integer STRING_PROMPT_TEXT = new Integer(2); + public static final Integer STRING_ERROR_TEXT = new Integer(3); + private Hashtable _hash_strings ; + + /** + * Option flags field + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + private BitField opt_data_type = new BitField(0x0000000F); + private BitField opt_error_style = new BitField(0x00000070); + private BitField opt_string_list_formula = new BitField(0x00000080); + private BitField opt_empty_cell_allowed = new BitField(0x00000100); + private BitField opt_surppres_dropdown_arrow = new BitField(0x00000200); + private BitField opt_show_prompt_on_cell_selected = new BitField(0x00040000); + private BitField opt_show_error_on_invalid_value = new BitField(0x00080000); + private BitField opt_condition_operator = new BitField(0x00F00000); + + public DVRecord() + { + } + + /** + * Constructs a DV record and sets its fields appropriately. + * + * @param in the RecordInputstream to read the record from + */ + + public DVRecord(RecordInputStream in) + { + super(in); + } + + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("NOT a valid DV RECORD"); + } + } + + protected void fillFields(RecordInputStream in) + { + field_rpn_token_1 = new Stack(); + field_rpn_token_2 = new Stack(); + + this.field_option_flags = in.readInt(); + this._hash_strings = new Hashtable(4); + + StringHandler strHandler_prompt_title = new StringHandler( in ); + this.field_title_prompt = strHandler_prompt_title.getStringData(); + this._hash_strings.put(DVRecord.STRING_PROMPT_TITLE, strHandler_prompt_title); + + StringHandler strHandler_error_title = new StringHandler( in ); + this.field_title_error = strHandler_error_title.getStringData(); + this._hash_strings.put(DVRecord.STRING_ERROR_TITLE, strHandler_error_title); + + StringHandler strHandler_prompt_text = new StringHandler( in ); + this.field_text_prompt = strHandler_prompt_text.getStringData(); + this._hash_strings.put(DVRecord.STRING_PROMPT_TEXT, strHandler_prompt_text); + + StringHandler strHandler_error_text = new StringHandler( in ); + this.field_text_error = strHandler_error_text.getStringData(); + this._hash_strings.put(DVRecord.STRING_ERROR_TEXT, strHandler_error_text); + + this.field_size_first_formula = in.readShort(); + this.field_not_used_1 = in.readShort(); + + //read first formula data condition + // Not sure if this was needed or not... +// try { +// in.skip(this.field_size_first_formula); +// } catch(IOException e) { throw new IllegalStateException(e); } + + int token_pos = 0; + while (token_pos < this.field_size_first_formula) + { + Ptg ptg = Ptg.createPtg(in); + token_pos += ptg.getSize(); + field_rpn_token_1.push(ptg); + } + + this.field_size_sec_formula = in.readShort(); + this.field_not_used_2 = in.readShort(); + + //read sec formula data condition + // Not sure if this was needed or not... + try { + in.skip(this.field_size_sec_formula); + } catch(IOException e) { throw new IllegalStateException(e); } + + token_pos = 0; + while (token_pos < this.field_size_sec_formula) + { + Ptg ptg = Ptg.createPtg(in); + token_pos += ptg.getSize(); + field_rpn_token_2.push(ptg); + } + + //read cell range address list with all affected ranges + this.field_regions = new HSSFCellRangeAddress(in); + } + + + // --> start option flags + /** + * set the condition data type + * @param type - condition data type + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setDataType(int type) + { + this.field_option_flags = this.opt_data_type.setValue(this.field_option_flags, type); + } + + /** + * get the condition data type + * @return the condition data type + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public int getDataType() + { + return this.opt_data_type.getValue(this.field_option_flags); + } + + /** + * set the condition error style + * @param type - condition error style + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setErrorStyle(int style) + { + this.field_option_flags = this.opt_error_style.setValue(this.field_option_flags, style); + } + + /** + * get the condition error style + * @return the condition error style + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public int getErrorStyle() + { + return this.opt_error_style.getValue(this.field_option_flags); + } + + /** + * set if in list validations the string list is explicitly given in the formula + * @param type - true if in list validations the string list is explicitly given in the formula; false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setListExplicitFormula(boolean explicit) + { + this.field_option_flags = this.opt_string_list_formula.setBoolean(this.field_option_flags, explicit); + } + + /** + * return true if in list validations the string list is explicitly given in the formula, false otherwise + * @return true if in list validations the string list is explicitly given in the formula, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public boolean getListExplicitFormula() + { + return (this.opt_string_list_formula.isSet(this.field_option_flags)); + } + + /** + * set if empty values are allowed in cells + * @param type - true if empty values are allowed in cells, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setEmptyCellAllowed(boolean allowed) + { + this.field_option_flags = this.opt_empty_cell_allowed.setBoolean(this.field_option_flags, allowed); + } + + /** + * return true if empty values are allowed in cells, false otherwise + * @return if empty values are allowed in cells, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public boolean getEmptyCellAllowed() + { + return (this.opt_empty_cell_allowed.isSet(this.field_option_flags)); + } + + /** + * set if drop down arrow should be surppressed when list validation is used + * @param type - true if drop down arrow should be surppressed when list validation is used, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setSurppresDropdownArrow(boolean surppress) + { + this.field_option_flags = this.opt_surppres_dropdown_arrow.setBoolean(this.field_option_flags, surppress); + } + + /** + * return true if drop down arrow should be surppressed when list validation is used, false otherwise + * @return if drop down arrow should be surppressed when list validation is used, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public boolean getSurppresDropdownArrow() + { + return (this.opt_surppres_dropdown_arrow.isSet(this.field_option_flags)); + } + + /** + * set if a prompt window should appear when cell is selected + * @param type - true if a prompt window should appear when cell is selected, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setShowPromptOnCellSelected(boolean show) + { + this.field_option_flags = this.opt_show_prompt_on_cell_selected.setBoolean(this.field_option_flags, show); + } + + /** + * return true if a prompt window should appear when cell is selected, false otherwise + * @return if a prompt window should appear when cell is selected, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public boolean getShowPromptOnCellSelected() + { + return (this.opt_show_prompt_on_cell_selected.isSet(this.field_option_flags)); + } + + /** + * set if an error window should appear when an invalid value is entered in the cell + * @param type - true if an error window should appear when an invalid value is entered in the cell, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setShowErrorOnInvalidValue(boolean show) + { + this.field_option_flags = this.opt_show_error_on_invalid_value.setBoolean(this.field_option_flags, show); + } + + /** + * return true if an error window should appear when an invalid value is entered in the cell, false otherwise + * @return if an error window should appear when an invalid value is entered in the cell, false otherwise + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public boolean getShowErrorOnInvalidValue() + { + return (this.opt_show_error_on_invalid_value.isSet(this.field_option_flags)); + } + + /** + * set the condition operator + * @param type - condition operator + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public void setConditionOperator(int operator) + { + this.field_option_flags = this.opt_condition_operator.setValue(this.field_option_flags, operator); + } + + /** + * get the condition operator + * @return the condition operator + * @see org.apache.poi.hssf.util.HSSFDataValidation utility class + */ + public int getConditionOperator() + { + return this.opt_condition_operator.getValue(this.field_option_flags); + } + // <-- end option flags + + public void setFirstFormulaRPN( Stack rpn ) + { + this.field_rpn_token_1 = rpn; + } + + public void setFirstFormulaSize( short size ) + { + this.field_size_first_formula = size; + } + + public void setSecFormulaRPN( Stack rpn ) + { + this.field_rpn_token_2 = rpn; + } + + public void setSecFormulaSize( short size ) + { + this.field_size_sec_formula = size; + } + + public void setStringField( Integer type, String str_data ) + { + if ( this._hash_strings == null ) + { + this._hash_strings = new Hashtable(); + } + StringHandler strHandler = new StringHandler(); + if ( str_data == null ) + { + str_data = ""; + } + else + { + strHandler.setStringLength(str_data.length()); + } + strHandler.setStringData(str_data); + + strHandler.setUnicodeFlag((byte)0x00); + this._hash_strings.put( type, strHandler); + } + + public String getStringField( Integer type ) + { + return ((StringHandler)this._hash_strings.get(type)).getStringData(); + } + + public void setCellRangeAddress( HSSFCellRangeAddress range ) + { + this.field_regions = range; + } + + public HSSFCellRangeAddress getCellRangeAddress( ) + { + return this.field_regions; + } + + /** + * gets the option flags field. + * @return options - the option flags field + */ + public int getOptionFlags() + { + return this.field_option_flags; + } + + public String toString() + { + /** @todo DVRecord string representation */ + StringBuffer buffer = new StringBuffer(); + + return buffer.toString(); + } + + public int serialize(int offset, byte [] data) + { + int size = this.getRecordSize(); + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, ( short ) (size-4)); + + int pos = 4; + LittleEndian.putInt(data, pos + offset, this.getOptionFlags()); + pos += 4; + pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_PROMPT_TITLE )).serialize(pos+offset, data); + pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_ERROR_TITLE )).serialize(pos+offset, data); + pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_PROMPT_TEXT )).serialize(pos+offset, data); + pos += ((StringHandler)this._hash_strings.get( DVRecord.STRING_ERROR_TEXT )).serialize(pos+offset, data); + LittleEndian.putShort(data, offset+pos, this.field_size_first_formula); + pos += 2; + LittleEndian.putShort(data, offset+pos, this.field_not_used_1); + pos += 2; + + for (int k = 0; k < this.field_rpn_token_1.size(); k++) + { + Ptg ptg = ( Ptg ) this.field_rpn_token_1.get(k); + ptg.writeBytes(data, pos+offset); + pos += ptg.getSize(); + } + + LittleEndian.putShort(data, offset+pos, this.field_size_sec_formula); + pos += 2; + LittleEndian.putShort(data, offset+pos, this.field_not_used_2); + pos += 2; + if ( this.field_size_sec_formula > 0 ) + { + for (int k = 0; k < this.field_rpn_token_2.size(); k++) + { + Ptg ptg = ( Ptg ) this.field_rpn_token_2.get(k); + ptg.writeBytes(data, pos+offset); + pos += ptg.getSize(); + } + } + this.field_regions.serialize(pos+offset, data); + return size; + } + + public int getRecordSize() + { + int size = 4+4+2+2+2+2;//header+options_field+first_formula_size+first_unused+sec_formula_size+sec+unused; + if ( this._hash_strings != null ) + { + Enumeration enum_keys = this._hash_strings.keys(); + while ( enum_keys.hasMoreElements() ) + { + size += ((StringHandler)this._hash_strings.get( (Integer)enum_keys.nextElement() )).getSize(); + } + } + size += this.field_size_first_formula+ this.field_size_sec_formula; + size += this.field_regions.getSize(); + return size; + } + + public short getSid() + { + return this.sid; + } + + /**@todo DVRecord = Serializare */ + + private class StringHandler + { + private int _string_length = 0x0001; + private byte _string_unicode_flag = 0x00; + private String _string_data = "0x00"; + private int _start_offset; + private int _end_offset; + + StringHandler() + { + + } + + StringHandler(RecordInputStream in) + { + this.fillFields(in); + } + + protected void fillFields(RecordInputStream in) + { + this._string_length = in.readUShort(); + this._string_unicode_flag = in.readByte(); + if (this._string_unicode_flag == 1) + { + this._string_data = in.readUnicodeLEString(this._string_length); + } + else + { + this._string_data = in.readCompressedUnicode(this._string_length); + } + } + + private void setStringData( String string_data ) + { + this._string_data = string_data; + } + + private String getStringData() + { + return this._string_data; + } + + private int getEndOffset() + { + return this._end_offset; + } + + public int serialize( int offset, byte[] data ) + { + LittleEndian.putUShort(data, offset, this._string_length ); + data[2 + offset] = this._string_unicode_flag; + if (this._string_unicode_flag == 1) + { + StringUtil.putUnicodeLE(this._string_data, data, 3 + offset); + } + else + { + StringUtil.putCompressedUnicode(this._string_data, data, 3 + offset); + } + return getSize(); + } + + private void setUnicodeFlag( byte flag ) + { + this._string_unicode_flag = flag; + } + + private void setStringLength( int len ) + { + this._string_length = len; + } + + private int getStringByteLength() + { + return (this._string_unicode_flag == 1) ? this._string_length * 2 : this._string_length; + } + + public int getSize() + { + return 2 + 1 + getStringByteLength(); + } + } +} diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 927d5f08be..984291cdb0 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -75,7 +75,8 @@ public class RecordFactory HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class, WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, - FileSharingRecord.class, ChartTitleFormatRecord.class + FileSharingRecord.class, ChartTitleFormatRecord.class, + DVRecord.class, DVALRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java b/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java index 745359d928..438f5e5968 100644 --- a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java +++ b/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java @@ -16,6 +16,7 @@ package org.apache.poi.hssf.util; +import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.util.LittleEndian; import java.util.ArrayList; @@ -56,29 +57,27 @@ public class HSSFCellRangeAddress * Construct a new HSSFCellRangeAddress object and sets its fields appropriately . * Even this isn't an Excel record , I kept the same behavior for reading/writing * the object's data as for a regular record . - * @param data Excel's file stream data - * @param offset the offset in Excel's file data + * + * @param in the RecordInputstream to read the record from */ - public HSSFCellRangeAddress( byte [] data, int offset ) + public HSSFCellRangeAddress(RecordInputStream in) { - this.fillFields(data, offset); + this.fillFields(in); } - public void fillFields(byte [] data, int offset) + public void fillFields(RecordInputStream in) { - this.field_addr_number = LittleEndian.getShort(data, 0 + offset); + this.field_addr_number = in.readShort(); this.field_regions_list = new ArrayList(this.field_addr_number); - int pos = 2; for (int k = 0; k < this.field_addr_number; k++) { - short first_row = LittleEndian.getShort(data, pos + offset); - short first_col = LittleEndian.getShort(data, pos + 2 + offset); - short last_row = LittleEndian.getShort(data, pos + 4 + offset); - short last_col = LittleEndian.getShort(data, pos + 6 + offset); + short first_row = in.readShort(); + short first_col = in.readShort(); + short last_row = in.readShort(); + short last_col = in.readShort(); AddrStructure region = new AddrStructure(first_row, first_col, last_row, last_col); - pos += 8; this.field_regions_list.add(region); } } diff --git a/src/testcases/org/apache/poi/hssf/data/templateExcelWithAutofilter.xls b/src/testcases/org/apache/poi/hssf/data/templateExcelWithAutofilter.xls new file mode 100644 index 0000000000000000000000000000000000000000..5813fde385ca4cb431c9f09c2f5e071ce2a7fb18 GIT binary patch literal 13824 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