From: Josh Micich Date: Fri, 12 Jun 2009 19:17:59 +0000 (+0000) Subject: Bugzilla 47363 - Fixed HSSFSheet to allow addition of data validations after sheet... X-Git-Tag: REL_3_5-FINAL~111 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6295a0d7937b08d0c74f8386a25e829d3be85911;p=poi.git Bugzilla 47363 - Fixed HSSFSheet to allow addition of data validations after sheet protection git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@784240 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 31466b4ced..7e23bc6fa2 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -35,6 +35,7 @@ + 47363 - Fixed HSSFSheet to allow addition of data validations after sheet protection 47294 - Fixed XSSFWorkbook#setRepeatingRowsAndColumns to tolerate sheet names with quotes 47309 - Fixed logic in HSSFCell.getCellComment to handle sheets with more than 65536 comments 46776 - Added clone() method to MulBlankRecord to fix crash in Sheet.cloneSheet() diff --git a/src/java/org/apache/poi/hssf/model/RecordOrderer.java b/src/java/org/apache/poi/hssf/model/RecordOrderer.java index c0cb33f4db..5740f20925 100644 --- a/src/java/org/apache/poi/hssf/model/RecordOrderer.java +++ b/src/java/org/apache/poi/hssf/model/RecordOrderer.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.record.CalcCountRecord; import org.apache.poi.hssf.record.CalcModeRecord; import org.apache.poi.hssf.record.DVALRecord; import org.apache.poi.hssf.record.DateWindow1904Record; +import org.apache.poi.hssf.record.DefaultColWidthRecord; import org.apache.poi.hssf.record.DefaultRowHeightRecord; import org.apache.poi.hssf.record.DeltaRecord; import org.apache.poi.hssf.record.DimensionsRecord; @@ -62,21 +63,23 @@ import org.apache.poi.hssf.record.UncalcedRecord; import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.WindowOneRecord; import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.aggregates.MergedCellsTable; import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; +import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; import org.apache.poi.hssf.record.pivottable.ViewDefinitionRecord; /** * Finds correct insert positions for records in workbook streams

- * + * * See OOO excelfileformat.pdf sec. 4.2.5 'Record Order in a BIFF8 Workbook Stream' - * + * * @author Josh Micich */ final class RecordOrderer { - + // TODO - simplify logic using a generalised record ordering private RecordOrderer() { @@ -84,7 +87,6 @@ final class RecordOrderer { } /** * Adds the specified new record in the correct place in sheet records list - * */ public static void addNewSheetRecord(List sheetRecords, RecordBase newRecord) { int index = findSheetInsertPos(sheetRecords, newRecord.getClass()); @@ -107,9 +109,69 @@ final class RecordOrderer { if (recClass == PageSettingsBlock.class) { return getPageBreakRecordInsertPos(records); } + if (recClass == WorksheetProtectionBlock.class) { + return getWorksheetProtectionBlockInsertPos(records); + } throw new RuntimeException("Unexpected record class (" + recClass.getName() + ")"); } + /** + * Finds the index where the protection block should be inserted + * @param records the records for this sheet + *

+	 * + BOF
+	 * o INDEX
+	 * o Calculation Settings Block
+	 * o PRINTHEADERS
+	 * o PRINTGRIDLINES
+	 * o GRIDSET
+	 * o GUTS
+	 * o DEFAULTROWHEIGHT
+	 * o SHEETPR
+	 * o Page Settings Block
+	 * o Worksheet Protection Block
+	 * o DEFCOLWIDTH
+	 * oo COLINFO
+	 * o SORT
+	 * + DIMENSION
+	 * 
+ */ + private static int getWorksheetProtectionBlockInsertPos(List records) { + int i = getDimensionsIndex(records); + while (i > 0) { + i--; + Object rb = records.get(i); + if (!isProtectionSubsequentRecord(rb)) { + return i+1; + } + } + throw new IllegalStateException("did not find insert pos for protection block"); + } + + + /** + * These records may occur between the 'Worksheet Protection Block' and DIMENSION: + *
+	 * o DEFCOLWIDTH
+	 * oo COLINFO
+	 * o SORT
+	 * 
+ */ + private static boolean isProtectionSubsequentRecord(Object rb) { + if (rb instanceof ColumnInfoRecordsAggregate) { + return true; // oo COLINFO + } + if (rb instanceof Record) { + Record record = (Record) rb; + switch (record.getSid()) { + case DefaultColWidthRecord.sid: + case UnknownRecord.SORT_0090: + return true; + } + } + return false; + } + private static int getPageBreakRecordInsertPos(List records) { int dimensionsIndex = getDimensionsIndex(records); int i = dimensionsIndex-1; @@ -163,7 +225,7 @@ final class RecordOrderer { if (rb instanceof DataValidityTable) { continue; } - + Record rec = (Record) rb; switch (rec.getSid()) { case WindowTwoRecord.sid: @@ -171,7 +233,7 @@ final class RecordOrderer { case PaneRecord.sid: case SelectionRecord.sid: case UnknownRecord.STANDARDWIDTH_0099: - // MergedCellsTable usually here + // MergedCellsTable usually here case UnknownRecord.LABELRANGES_015F: case UnknownRecord.PHONETICPR_00EF: // ConditionalFormattingTable goes here @@ -187,13 +249,13 @@ final class RecordOrderer { for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record Object rb = records.get(i); if (!(rb instanceof Record)) { - // DataValidityTable, ConditionalFormattingTable, + // DataValidityTable, ConditionalFormattingTable, // even PageSettingsBlock (which doesn't normally appear after 'View Settings') - continue; + continue; } Record rec = (Record) rb; switch (rec.getSid()) { - // 'View Settings' (4 records) + // 'View Settings' (4 records) case WindowTwoRecord.sid: case SCLRecord.sid: case PaneRecord.sid: @@ -206,11 +268,11 @@ final class RecordOrderer { throw new RuntimeException("Did not find Window2 record"); } - + /** * Finds the index where the sheet validations header record should be inserted * @param records the records for this sheet - * + * * + WINDOW2 * o SCL * o PANE @@ -340,7 +402,7 @@ final class RecordOrderer { } /** * @return true if the specified record ID terminates a sequence of Row block records - * It is assumed that at least one row or cell value record has been found prior to the current + * It is assumed that at least one row or cell value record has been found prior to the current * record */ public static boolean isEndOfRowBlock(int sid) { @@ -359,7 +421,7 @@ final class RecordOrderer { case DVALRecord.sid: return true; - case EOFRecord.sid: + case EOFRecord.sid: // WINDOW2 should always be present, so shouldn't have got this far throw new RuntimeException("Found EOFRecord before WindowTwoRecord was encountered"); } @@ -367,13 +429,13 @@ final class RecordOrderer { } /** - * @return true if the specified record id normally appears in the row blocks section + * @return true if the specified record id normally appears in the row blocks section * of the sheet records */ public static boolean isRowBlockRecord(int sid) { switch (sid) { case RowRecord.sid: - + case BlankRecord.sid: case BoolErrRecord.sid: case FormulaRecord.sid: diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index c8e2e33826..916272ecaf 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -42,19 +42,15 @@ import org.apache.poi.hssf.record.IterationRecord; import org.apache.poi.hssf.record.MergeCellsRecord; import org.apache.poi.hssf.record.NoteRecord; import org.apache.poi.hssf.record.ObjRecord; -import org.apache.poi.hssf.record.ObjectProtectRecord; import org.apache.poi.hssf.record.PaneRecord; -import org.apache.poi.hssf.record.PasswordRecord; import org.apache.poi.hssf.record.PrintGridlinesRecord; import org.apache.poi.hssf.record.PrintHeadersRecord; -import org.apache.poi.hssf.record.ProtectRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordBase; import org.apache.poi.hssf.record.RefModeRecord; import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.SaveRecalcRecord; -import org.apache.poi.hssf.record.ScenarioProtectRecord; import org.apache.poi.hssf.record.SelectionRecord; import org.apache.poi.hssf.record.UncalcedRecord; import org.apache.poi.hssf.record.WSBoolRecord; @@ -68,6 +64,7 @@ import org.apache.poi.hssf.record.aggregates.MergedCellsTable; import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; import org.apache.poi.hssf.record.aggregates.RecordAggregate; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; +import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor; import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.hssf.record.formula.FormulaShifter; @@ -113,11 +110,11 @@ public final class Sheet implements Model { protected DefaultRowHeightRecord defaultrowheight = null; private PageSettingsBlock _psBlock; - // 'Worksheet Protection Block' - protected ProtectRecord protect = null; - protected ObjectProtectRecord objprotect = null; - protected ScenarioProtectRecord scenprotect = null; - protected PasswordRecord _password = null; + /** + * 'Worksheet Protection Block'
+ * Aggregate object is always present, but possibly empty. + */ + private final WorksheetProtectionBlock _protectionBlock = new WorksheetProtectionBlock(); protected WindowTwoRecord windowTwo = null; protected SelectionRecord _selection = null; @@ -227,6 +224,11 @@ public final class Sheet implements Model { continue; } + if (WorksheetProtectionBlock.isComponentRecord(recSid)) { + _protectionBlock.addRecords(rs); + continue; + } + if (recSid == MergeCellsRecord.sid) { // when the MergedCellsTable is found in the right place, we expect those records to be contiguous _mergedCellsTable.read(rs); @@ -299,22 +301,6 @@ public final class Sheet implements Model { { windowTwo = (WindowTwoRecord) rec; } - else if ( recSid == ProtectRecord.sid ) - { - protect = (ProtectRecord) rec; - } - else if ( recSid == ObjectProtectRecord.sid ) - { - objprotect = (ObjectProtectRecord) rec; - } - else if ( recSid == ScenarioProtectRecord.sid ) - { - scenprotect = (ScenarioProtectRecord) rec; - } - else if ( recSid == PasswordRecord.sid ) - { - _password = (PasswordRecord) rec; - } else if ( recSid == GutsRecord.sid ) { _gutsRecord = (GutsRecord) rec; @@ -348,6 +334,7 @@ public final class Sheet implements Model { _rowsAggregate = rra; // put merged cells table in the right place (regardless of where the first MergedCellsRecord was found */ RecordOrderer.addNewSheetRecord(records, _mergedCellsTable); + RecordOrderer.addNewSheetRecord(records, _protectionBlock); if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited"); } @@ -432,7 +419,7 @@ public final class Sheet implements Model { records.add(_psBlock); // 'Worksheet Protection Block' (after 'Page Settings Block' and before DEFCOLWIDTH) - // PROTECT record normally goes here, don't add yet since the flag is initially false + records.add(_protectionBlock); // initially empty defaultcolwidth = createDefaultColWidth(); records.add( defaultcolwidth); @@ -1407,62 +1394,11 @@ public final class Sheet implements Model { } /** - * creates an ObjectProtect record with protect set to false. - */ - private static ObjectProtectRecord createObjectProtect() { - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "create protect record with protection disabled"); - ObjectProtectRecord retval = new ObjectProtectRecord(); - - retval.setProtect(false); - return retval; - } - - /** - * creates a ScenarioProtect record with protect set to false. - */ - private static ScenarioProtectRecord createScenarioProtect() { - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "create protect record with protection disabled"); - ScenarioProtectRecord retval = new ScenarioProtectRecord(); - - retval.setProtect(false); - return retval; - } - - /** - * @return the ProtectRecord. If one is not contained in the sheet, then one is created. - */ - public ProtectRecord getProtect() { - if (protect == null) { - protect = new ProtectRecord(false); - // Insert the newly created protect record just before DefaultColWidthRecord - int loc = findFirstRecordLocBySid(DefaultColWidthRecord.sid); - _records.add(loc, protect); - } - return protect; - } - - /** - * @return the PasswordRecord. If one is not contained in the sheet, then one is created. + * @return the {@link WorksheetProtectionBlock} for this sheet */ - public PasswordRecord getPassword() { - if (_password == null) { - _password = createPassword(); - //Insert the newly created password record at the end of the record (just before the EOF) - int loc = findFirstRecordLocBySid(EOFRecord.sid); - _records.add(loc, _password); - } - return _password; + public WorksheetProtectionBlock getProtectionBlock() { + return _protectionBlock; } - - /** - * creates a Password record with password set to 0x0000. - */ - private static PasswordRecord createPassword() { - return new PasswordRecord(0x0000); - } - /** * Sets whether the gridlines are shown in a viewer. * @param show whether to show gridlines or not @@ -1601,68 +1537,6 @@ public final class Sheet implements Model { } } - /** - * protect a spreadsheet with a password (not encypted, just sets protect - * flags and the password. - * @param password to set - * @param objects are protected - * @param scenarios are protected - */ - public void protectSheet( String password, boolean objects, boolean scenarios ) { - int protIdx = -1; - ProtectRecord prec = getProtect(); - PasswordRecord pass = getPassword(); - prec.setProtect(true); - pass.setPassword(PasswordRecord.hashPassword(password)); - if((objprotect == null && objects) || (scenprotect != null && scenarios)) { - protIdx = _records.indexOf( protect ); - } - if(objprotect == null && objects) { - ObjectProtectRecord rec = createObjectProtect(); - rec.setProtect(true); - _records.add(protIdx+1,rec); - objprotect = rec; - } - if(scenprotect == null && scenarios) { - ScenarioProtectRecord srec = createScenarioProtect(); - srec.setProtect(true); - _records.add(protIdx+2,srec); - scenprotect = srec; - } - } - - /** - * unprotect objects in the sheet (will not protect them, but any set to false are - * unprotected. - * @param sheet is unprotected (false = unprotect) - * @param objects are unprotected (false = unprotect) - * @param scenarios are unprotected (false = unprotect) - */ - public void unprotectSheet( boolean sheet, boolean objects, boolean scenarios ) { - - if (!sheet) { - ProtectRecord prec = getProtect(); - prec.setProtect(sheet); - PasswordRecord pass = getPassword(); - pass.setPassword((short)00); - } - if(objprotect != null && !objects) { - objprotect.setProtect(false); - } - if(scenprotect != null && !scenarios) { - scenprotect.setProtect(false); - } - } - - /** - * @return {sheet is protected, objects are proteced, scenarios are protected} - */ - public boolean[] isProtected() { - return new boolean[] { (protect != null && protect.getProtect()), - (objprotect != null && objprotect.getProtect()), - (scenprotect != null && scenprotect.getProtect())}; - } - public void groupRowRange(int fromRow, int toRow, boolean indent) { diff --git a/src/java/org/apache/poi/hssf/record/UnknownRecord.java b/src/java/org/apache/poi/hssf/record/UnknownRecord.java index b6a0d9040a..607eb917ce 100644 --- a/src/java/org/apache/poi/hssf/record/UnknownRecord.java +++ b/src/java/org/apache/poi/hssf/record/UnknownRecord.java @@ -44,6 +44,7 @@ public final class UnknownRecord extends StandardRecord { public static final int PRINTSIZE_0033 = 0x0033; public static final int PLS_004D = 0x004D; public static final int SHEETPR_0081 = 0x0081; + public static final int SORT_0090 = 0x0090; public static final int STANDARDWIDTH_0099 = 0x0099; public static final int SCL_00A0 = 0x00A0; public static final int BITMAP_00E9 = 0x00E9; @@ -56,7 +57,7 @@ public final class UnknownRecord extends StandardRecord { public static final int SHEETPROTECTION_0867 = 0x0867; public static final int RANGEPROTECTION_0868 = 0x0868; public static final int HEADER_FOOTER_089C = 0x089C; - + private int _sid; private byte[] _rawData; @@ -65,8 +66,8 @@ public final class UnknownRecord extends StandardRecord { * @param data the data */ public UnknownRecord(int id, byte[] data) { - _sid = id & 0xFFFF; - _rawData = data; + _sid = id & 0xFFFF; + _rawData = data; } @@ -122,12 +123,12 @@ public final class UnknownRecord extends StandardRecord { /** * These BIFF record types are known but still uninterpreted by POI - * + * * @return the documented name of this BIFF record type, null if unknown to POI */ public static String getBiffName(int sid) { // Note to POI developers: - // Make sure you delete the corresponding entry from + // Make sure you delete the corresponding entry from // this method any time a new Record subclass is created. switch (sid) { case PRINTSIZE_0033: return "PRINTSIZE"; @@ -135,7 +136,7 @@ public final class UnknownRecord extends StandardRecord { case 0x0050: return "DCON"; // Data Consolidation Information case 0x007F: return "IMDATA"; case SHEETPR_0081: return "SHEETPR"; - case 0x0090: return "SORT"; // Sorting Options + case SORT_0090: return "SORT"; // Sorting Options case 0x0094: return "LHRECORD"; // .WK? File Conversion Information case STANDARDWIDTH_0099: return "STANDARDWIDTH"; //Standard Column Width case 0x009D: return "AUTOFILTERINFO"; // Drop-Down Arrow Count @@ -145,7 +146,7 @@ public final class UnknownRecord extends StandardRecord { case 0x00B2: return "SXVI"; // (pivot table) View Item case 0x00B4: return "SXIVD"; // (pivot table) Row/Column Field IDs case 0x00B5: return "SXLI"; // (pivot table) Line Item Array - + case 0x00D3: return "OBPROJ"; case 0x00DC: return "PARAMQRY"; case 0x00DE: return "OLESIZE"; @@ -181,7 +182,7 @@ public final class UnknownRecord extends StandardRecord { case 0x087B: return "CFEX"; case 0x087C: return "XFCRC"; case 0x087D: return "XFEXT"; - case 0x087F: return "CONTINUEFRT12"; + case 0x087F: return "CONTINUEFRT12"; case 0x088B: return "PLV"; case 0x088C: return "COMPAT12"; case 0x088D: return "DXF"; @@ -209,12 +210,11 @@ public final class UnknownRecord extends StandardRecord { } /** - * * @return true if the unknown record id has been observed in POI unit tests */ private static boolean isObservedButUnknown(int sid) { switch (sid) { - case 0x0033: + case 0x0033: // contains 2 bytes of data: 0x0001 or 0x0003 case 0x0034: // Seems to be written by MSAccess @@ -222,7 +222,7 @@ public final class UnknownRecord extends StandardRecord { // appears after last cell value record and before WINDOW2 case 0x01BD: case 0x01C2: - // Written by Excel 2007 + // Written by Excel 2007 // rawData is multiple of 12 bytes long // appears after last cell value record and before WINDOW2 or drawing records case 0x089D: diff --git a/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java new file mode 100644 index 0000000000..f838485f74 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java @@ -0,0 +1,245 @@ +/* ==================================================================== + 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.aggregates; + +import org.apache.poi.hssf.model.RecordStream; +import org.apache.poi.hssf.record.ObjectProtectRecord; +import org.apache.poi.hssf.record.PasswordRecord; +import org.apache.poi.hssf.record.ProtectRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.hssf.record.ScenarioProtectRecord; + +/** + * Groups the sheet protection records for a worksheet. + *

+ * + * See OOO excelfileformat.pdf sec 4.18.2 'Sheet Protection in a Workbook + * (BIFF5-BIFF8)' + * + * @author Josh Micich + */ +public final class WorksheetProtectionBlock extends RecordAggregate { + // Every one of these component records is optional + // (The whole WorksheetProtectionBlock may not be present) + private ProtectRecord _protectRecord; + private ObjectProtectRecord _objectProtectRecord; + private ScenarioProtectRecord _scenarioProtectRecord; + private PasswordRecord _passwordRecord; + + /** + * Creates an empty WorksheetProtectionBlock + */ + public WorksheetProtectionBlock() { + // all fields empty + } + + /** + * @return true if the specified Record sid is one belonging to + * the 'Page Settings Block'. + */ + public static boolean isComponentRecord(int sid) { + switch (sid) { + case ProtectRecord.sid: + case ObjectProtectRecord.sid: + case ScenarioProtectRecord.sid: + case PasswordRecord.sid: + return true; + } + return false; + } + + private boolean readARecord(RecordStream rs) { + switch (rs.peekNextSid()) { + case ProtectRecord.sid: + checkNotPresent(_protectRecord); + _protectRecord = (ProtectRecord) rs.getNext(); + break; + case ObjectProtectRecord.sid: + checkNotPresent(_objectProtectRecord); + _objectProtectRecord = (ObjectProtectRecord) rs.getNext(); + break; + case ScenarioProtectRecord.sid: + checkNotPresent(_scenarioProtectRecord); + _scenarioProtectRecord = (ScenarioProtectRecord) rs.getNext(); + break; + case PasswordRecord.sid: + checkNotPresent(_passwordRecord); + _passwordRecord = (PasswordRecord) rs.getNext(); + break; + default: + // all other record types are not part of the PageSettingsBlock + return false; + } + return true; + } + + private void checkNotPresent(Record rec) { + if (rec != null) { + throw new RecordFormatException("Duplicate PageSettingsBlock record (sid=0x" + + Integer.toHexString(rec.getSid()) + ")"); + } + } + + public void visitContainedRecords(RecordVisitor rv) { + // Replicates record order from Excel 2007, though this is not critical + + visitIfPresent(_protectRecord, rv); + visitIfPresent(_objectProtectRecord, rv); + visitIfPresent(_scenarioProtectRecord, rv); + visitIfPresent(_passwordRecord, rv); + } + + private static void visitIfPresent(Record r, RecordVisitor rv) { + if (r != null) { + rv.visitRecord(r); + } + } + + public PasswordRecord getPasswordRecord() { + return _passwordRecord; + } + + public ScenarioProtectRecord getHCenter() { + return _scenarioProtectRecord; + } + + /** + * This method reads {@link WorksheetProtectionBlock} records from the supplied RecordStream + * until the first non-WorksheetProtectionBlock record is encountered. As each record is read, + * it is incorporated into this WorksheetProtectionBlock. + *

+ * As per the OOO documentation, the protection block records can be expected to be written + * together (with no intervening records), but earlier versions of POI (prior to Jun 2009) + * didn't do this. Workbooks with sheet protection created by those earlier POI versions + * seemed to be valid (Excel opens them OK). So PO allows continues to support reading of files + * with non continuous worksheet protection blocks. + * + *

+ * Note - when POI writes out this WorksheetProtectionBlock, the records will always be + * written in one consolidated block (in the standard ordering) regardless of how scattered the + * records were when they were originally read. + */ + public void addRecords(RecordStream rs) { + while (true) { + if (!readARecord(rs)) { + break; + } + } + } + + /** + * @return the ProtectRecord. If one is not contained in the sheet, then one + * is created. + */ + private ProtectRecord getProtect() { + if (_protectRecord == null) { + _protectRecord = new ProtectRecord(false); + } + return _protectRecord; + } + + /** + * @return the PasswordRecord. If one is not contained in the sheet, then + * one is created. + */ + private PasswordRecord getPassword() { + if (_passwordRecord == null) { + _passwordRecord = createPassword(); + } + return _passwordRecord; + } + + /** + * protect a spreadsheet with a password (not encrypted, just sets protect + * flags and the password. + * + * @param password to set. Pass null to remove all protection + * @param shouldProtectObjects are protected + * @param shouldProtectScenarios are protected + */ + public void protectSheet(String password, boolean shouldProtectObjects, + boolean shouldProtectScenarios) { + if (password == null) { + _passwordRecord = null; + _protectRecord = null; + _objectProtectRecord = null; + _scenarioProtectRecord = null; + return; + } + + ProtectRecord prec = getProtect(); + PasswordRecord pass = getPassword(); + prec.setProtect(true); + pass.setPassword(PasswordRecord.hashPassword(password)); + if (_objectProtectRecord == null && shouldProtectObjects) { + ObjectProtectRecord rec = createObjectProtect(); + rec.setProtect(true); + _objectProtectRecord = rec; + } + if (_scenarioProtectRecord == null && shouldProtectScenarios) { + ScenarioProtectRecord srec = createScenarioProtect(); + srec.setProtect(true); + _scenarioProtectRecord = srec; + } + } + + public boolean isSheetProtected() { + return _protectRecord != null && _protectRecord.getProtect(); + } + + public boolean isObjectProtected() { + return _objectProtectRecord != null && _objectProtectRecord.getProtect(); + } + + public boolean isScenarioProtected() { + return _scenarioProtectRecord != null && _scenarioProtectRecord.getProtect(); + } + + /** + * creates an ObjectProtect record with protect set to false. + */ + private static ObjectProtectRecord createObjectProtect() { + ObjectProtectRecord retval = new ObjectProtectRecord(); + retval.setProtect(false); + return retval; + } + + /** + * creates a ScenarioProtect record with protect set to false. + */ + private static ScenarioProtectRecord createScenarioProtect() { + ScenarioProtectRecord retval = new ScenarioProtectRecord(); + retval.setProtect(false); + return retval; + } + + /** + * creates a Password record with password set to 0x0000. + */ + private static PasswordRecord createPassword() { + return new PasswordRecord(0x0000); + } + + public int getPasswordHash() { + if (_passwordRecord == null) { + return 0; + } + return _passwordRecord.getPassword(); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 83474957e6..f4501e619f 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -44,6 +44,7 @@ import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.WSBoolRecord; import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.hssf.record.aggregates.DataValidityTable; +import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; import org.apache.poi.hssf.record.formula.FormulaShifter; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.hssf.util.Region; @@ -126,7 +127,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @return the parent workbook */ public HSSFWorkbook getWorkbook(){ - return _workbook; + return _workbook; } /** @@ -162,7 +163,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { hrow = getRow( cval.getRow() ); lastrow = hrow; if (hrow == null) { - // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords + // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords // Excel, OpenOffice.org and GoogleDocs are all OK with this, so POI should be too. if (rowRecordsAlreadyPresent) { // if at least one row record is present, all should be present. @@ -180,7 +181,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if (log.check( POILogger.DEBUG )) log.log( DEBUG, "record took ", new Long( System.currentTimeMillis() - cellstart ) ); - + } if (log.check( POILogger.DEBUG )) log.log(DEBUG, "total sheet cell creation took ", @@ -340,12 +341,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Gets the number last row on the sheet. * Owing to idiosyncrasies in the excel file * format, if the result of calling this method - * is zero, you can't tell if that means there + * is zero, you can't tell if that means there * are zero rows on the sheet, or one at * position zero. For that case, additionally * call {@link #getPhysicalNumberOfRows()} to * tell if there is a row at position zero - * or not. + * or not. * @return the number of the last row contained in this sheet, zero based. */ public int getLastRowNum() { @@ -504,20 +505,20 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { { _sheet.setDefaultRowHeight((short) (height * 20)); } - + /** * Returns the HSSFCellStyle that applies to the given * (0 based) column, or null if no style has been * set for that column */ public HSSFCellStyle getColumnStyle(int column) { - short styleIndex = _sheet.getXFIndexForColAt((short)column); - - if(styleIndex == 0xf) { - // None set - return null; - } - + short styleIndex = _sheet.getXFIndexForColAt((short)column); + + if(styleIndex == 0xf) { + // None set + return null; + } + ExtendedFormatRecord xf = _book.getExFormatAt(styleIndex); return new HSSFCellStyle(styleIndex, xf, _book); } @@ -660,8 +661,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { */ public Region getMergedRegionAt(int index) { CellRangeAddress cra = getMergedRegion(index); - - return new Region(cra.getFirstRow(), (short)cra.getFirstColumn(), + + return new Region(cra.getFirstRow(), (short)cra.getFirstColumn(), cra.getLastRow(), (short)cra.getLastColumn()); } /** @@ -858,7 +859,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @param value whether to display or hide all zero values on the worksheet */ public void setDisplayZeros(boolean value){ - _sheet.getWindowTwo().setDisplayZeros(value); + _sheet.getWindowTwo().setDisplayZeros(value); } /** @@ -967,19 +968,22 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { _sheet.getPageSettings().setMargin(margin, size); } + private WorksheetProtectionBlock getProtectionBlock() { + return _sheet.getProtectionBlock(); + } /** * Answer whether protection is enabled or disabled * @return true => protection enabled; false => protection disabled */ public boolean getProtect() { - return getSheet().isProtected()[0]; + return getProtectionBlock().isSheetProtected(); } /** * @return hashed password */ public short getPassword() { - return (short)getSheet().getPassword().getPassword(); + return (short)getProtectionBlock().getPasswordHash(); } /** @@ -987,7 +991,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @return true => protection enabled; false => protection disabled */ public boolean getObjectProtect() { - return getSheet().isProtected()[1]; + return getProtectionBlock().isObjectProtected(); } /** @@ -995,24 +999,14 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @return true => protection enabled; false => protection disabled */ public boolean getScenarioProtect() { - return getSheet().isProtected()[2]; - } - - /** - * Sets the protection on enabled or disabled - * @param protect true => protection enabled; false => protection disabled - * @deprecated (Jul 2007) use {@link #protectSheet(String)} - */ - public void setProtect(boolean protect) { - getSheet().getProtect().setProtect(protect); + return getProtectionBlock().isScenarioProtected(); } - /** * Sets the protection enabled as well as the password - * @param password to set for protection + * @param password to set for protection. Pass null to remove protection */ public void protectSheet(String password) { - getSheet().protectSheet(password, true, true); //protect objs&scenarios(normal) + getProtectionBlock().protectSheet(password, true, true); //protect objs&scenarios(normal) } /** @@ -1153,7 +1147,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { shiftRows(startRow, endRow, n, copyRowHeight, resetOriginalRowHeight, true); } - + /** * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. @@ -1171,7 +1165,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @param resetOriginalRowHeight whether to set the original row's height to the default * @param moveComments whether to move comments at the same time as the cells they are attached to */ - public void shiftRows(int startRow, int endRow, int n, + public void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) { int s, inc; if (n < 0) { @@ -1183,7 +1177,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } NoteRecord[] noteRecs; if (moveComments) { - noteRecs = _sheet.getNoteRecords(); + noteRecs = _sheet.getNoteRecords(); } else { noteRecs = NoteRecord.EMPTY_ARRAY; } @@ -1197,10 +1191,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if ( row2Replace == null ) row2Replace = createRow( rowNum + n ); - + // Remove all the old cells from the row we'll - // be writing too, before we start overwriting - // any cells. This avoids issues with cells + // be writing too, before we start overwriting + // any cells. This avoids issues with cells // changing type, and records not being correctly // overwritten row2Replace.removeAllCells(); @@ -1235,7 +1229,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } // Now zap all the cells in the source row row.removeAllCells(); - + // Move comments from the source row to the // destination row. Note that comments can // exist for cells which are null @@ -1476,10 +1470,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { for (Iterator iterator = escherRecords.iterator(); iterator.hasNext();) { EscherRecord escherRecord = iterator.next(); if (fat) { - System.out.println(escherRecord.toString()); - } else { - escherRecord.display(w, 0); - } + System.out.println(escherRecord.toString()); + } else { + escherRecord.display(w, 0); + } } w.flush(); } @@ -1533,7 +1527,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { EscherAggregate agg = (EscherAggregate) _sheet.findFirstRecordBySid(EscherAggregate.sid); return agg; } - + /** * Returns the top-level drawing patriach, if there is * one. @@ -1549,7 +1543,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { public HSSFPatriarch getDrawingPatriarch() { EscherAggregate agg = getDrawingEscherAggregate(); if(agg == null) return null; - + HSSFPatriarch patriarch = new HSSFPatriarch(this, agg); agg.setPatriarch(patriarch); @@ -1649,7 +1643,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { public void autoSizeColumn(int column) { autoSizeColumn(column, false); } - + /** * Adjusts the column width to fit the contents. * @@ -1657,9 +1651,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * normally only be called once per column, at the end of your * processing. * - * You can specify whether the content of merged cells should be considered or ignored. + * You can specify whether the content of merged cells should be considered or ignored. * Default is to ignore merged cells. - * + * * @param column the column index * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column */ @@ -1672,13 +1666,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * '0' looks to be a good choice. */ char defaultChar = '0'; - + /** * This is the multiple that the font height is scaled by when determining the * boundary of rotated text. */ double fontHeightMultiple = 2.0; - + FontRenderContext frc = new FontRenderContext(null, true, true); HSSFWorkbook wb = new HSSFWorkbook(_book); @@ -1704,7 +1698,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { CellRangeAddress region = getMergedRegion(i); if (containsCell(region, row.getRowNum(), column)) { if (!useMergedCells) { - // If we're not using merged cells, skip this one and move on to the next. + // If we're not using merged cells, skip this one and move on to the next. continue rows; } cell = row.getCell(region.getFirstColumn()); @@ -1715,7 +1709,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { HSSFCellStyle style = cell.getCellStyle(); int cellType = cell.getCellType(); if(cellType == HSSFCell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType(); - + HSSFFont font = wb.getFontAt(style.getFontIndex()); if (cellType == HSSFCell.CELL_TYPE_STRING) { @@ -1836,9 +1830,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if(c != null) { return c.getCellComment(); } - // No cell, so you will get new - // objects every time, sorry... - return HSSFCell.findCellComment(_sheet, row, column); + // No cell, so you will get new + // objects every time, sorry... + return HSSFCell.findCellComment(_sheet, row, column); } return null; } diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index a16c278e5a..a102508a0d 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -644,15 +644,14 @@ public final class TestSheet extends TestCase { Sheet sheet = Sheet.createSheet(); List sheetRecs = sheet.getRecords(); - assertEquals(22, sheetRecs.size()); + assertEquals(23, sheetRecs.size()); FormulaShifter shifter = FormulaShifter.createForRowShift(0, 0, 0, 1); sheet.updateFormulasAfterCellShift(shifter, 0); - if (sheetRecs.size() == 23 && sheetRecs.get(21) instanceof ConditionalFormattingTable) { + if (sheetRecs.size() == 24 && sheetRecs.get(22) instanceof ConditionalFormattingTable) { throw new AssertionFailedError("Identified bug 46547a"); } - assertEquals(22, sheetRecs.size()); - + assertEquals(23, sheetRecs.size()); } /** * Bug 46547 happened when attempting to add conditional formatting to a sheet diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index abed228deb..2f944c5fd7 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -28,7 +28,10 @@ import org.apache.poi.hssf.HSSFITestDataProvider; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; +import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector; import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.usermodel.BaseTestSheet; import org.apache.poi.ddf.EscherDgRecord; @@ -125,7 +128,7 @@ public final class TestHSSFSheet extends BaseTestSheet { s.setRowSumsRight(true); // Check - assertEquals(true, record.getAlternateExpression()); //sheet.setRowSumsBelow alters this field too + assertEquals(true, record.getAlternateExpression()); //sheet.setRowSumsBelow alters this field too assertEquals(false, record.getAlternateFormula()); assertEquals(true, record.getAutobreaks()); assertEquals(true, record.getDialog()); @@ -300,37 +303,104 @@ public final class TestHSSFSheet extends BaseTestSheet { /** * Test that the ProtectRecord is included when creating or cloning a sheet */ - public void testProtect() { + public void testCloneWithProtect() { + String passwordA = "secrect"; + int expectedHashA = -6810; + String passwordB = "admin"; + int expectedHashB = -14556; HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet hssfSheet = workbook.createSheet(); - Sheet sheet = hssfSheet.getSheet(); - ProtectRecord protect = sheet.getProtect(); - - assertFalse(protect.getProtect()); - - // This will tell us that cloneSheet, and by extension, - // the list forms of createSheet leave us with an accessible - // ProtectRecord. - hssfSheet.protectSheet("secret"); - Sheet cloned = sheet.cloneSheet(); - assertNotNull(cloned.getProtect()); - assertTrue(hssfSheet.getProtect()); + hssfSheet.protectSheet(passwordA); + + assertEquals(expectedHashA, hssfSheet.getSheet().getProtectionBlock().getPasswordHash()); + + // Clone the sheet, and make sure the password hash is preserved + HSSFSheet sheet2 = workbook.cloneSheet(0); + assertEquals(expectedHashA, sheet2.getSheet().getProtectionBlock().getPasswordHash()); + + // change the password on the first sheet + hssfSheet.protectSheet(passwordB); + assertEquals(expectedHashB, hssfSheet.getSheet().getProtectionBlock().getPasswordHash()); + // but the cloned sheet's password should remain unchanged + assertEquals(expectedHashA, sheet2.getSheet().getProtectionBlock().getPasswordHash()); } public void testProtectSheet() { - short expected = (short)0xfef1; + int expectedHash = (short)0xfef1; HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet s = wb.createSheet(); s.protectSheet("abcdefghij"); - Sheet sheet = s.getSheet(); - ProtectRecord protect = sheet.getProtect(); - PasswordRecord pass = sheet.getPassword(); - assertTrue("protection should be on",protect.getProtect()); - assertTrue("object protection should be on",sheet.isProtected()[1]); - assertTrue("scenario protection should be on",sheet.isProtected()[2]); - assertEquals("well known value for top secret hash should be "+Integer.toHexString(expected).substring(4),expected,pass.getPassword()); + WorksheetProtectionBlock pb = s.getSheet().getProtectionBlock(); + assertTrue("protection should be on", pb.isSheetProtected()); + assertTrue("object protection should be on",pb.isObjectProtected()); + assertTrue("scenario protection should be on",pb.isScenarioProtected()); + assertEquals("well known value for top secret hash should be "+Integer.toHexString(expectedHash).substring(4), expectedHash, pb.getPasswordHash()); } + /** + * {@link PasswordRecord} belongs with the rest of the Worksheet Protection Block + * (which should be before {@link DimensionsRecord}). + */ + public void testProtectSheetRecordOrder_bug47363a() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet s = wb.createSheet(); + s.protectSheet("secret"); + RecordCollector rc = new RecordCollector(); + s.getSheet().visitContainedRecords(rc, 0); + Record[] recs = rc.getRecords(); + int nRecs = recs.length; + if (recs[nRecs-2] instanceof PasswordRecord && recs[nRecs-5] instanceof DimensionsRecord) { + throw new AssertionFailedError("Identified bug 47363a - PASSWORD after DIMENSION"); + } + // Check that protection block is together, and before DIMENSION + confirmRecordClass(recs, nRecs-4, DimensionsRecord.class); + confirmRecordClass(recs, nRecs-9, ProtectRecord.class); + confirmRecordClass(recs, nRecs-8, ObjectProtectRecord.class); + confirmRecordClass(recs, nRecs-7, ScenarioProtectRecord.class); + confirmRecordClass(recs, nRecs-6, PasswordRecord.class); + } + + private static void confirmRecordClass(Record[] recs, int index, Class cls) { + if (recs.length <= index) { + throw new AssertionFailedError("Expected (" + cls.getName() + ") at index " + + index + " but array length is " + recs.length + "."); + } + assertEquals(cls, recs[index].getClass()); + } + + /** + * There should be no problem with adding data validations after sheet protection + */ + public void testDvProtectionOrder_bug47363b() { + HSSFWorkbook workbook = new HSSFWorkbook(); + HSSFSheet sheet = workbook.createSheet("Sheet1"); + sheet.protectSheet("secret"); + + DVConstraint dvc = DVConstraint.createNumericConstraint(DVConstraint.ValidationType.INTEGER, + DVConstraint.OperatorType.BETWEEN, "10", "100"); + CellRangeAddressList numericCellAddressList = new CellRangeAddressList(0, 0, 1, 1); + HSSFDataValidation dv = new HSSFDataValidation(numericCellAddressList, dvc); + try { + sheet.addValidationData(dv); + } catch (IllegalStateException e) { + String expMsg = "Unexpected (org.apache.poi.hssf.record.PasswordRecord) while looking for DV Table insert pos"; + if (expMsg.equals(e.getMessage())) { + throw new AssertionFailedError("Identified bug 47363b"); + } + throw e; + } + RecordCollector rc; + rc = new RecordCollector(); + sheet.getSheet().visitContainedRecords(rc, 0); + int nRecsWithProtection = rc.getRecords().length; + + sheet.protectSheet(null); + rc = new RecordCollector(); + sheet.getSheet().visitContainedRecords(rc, 0); + int nRecsWithoutProtection = rc.getRecords().length; + + assertEquals(4, nRecsWithProtection - nRecsWithoutProtection); + } public void testZoom() { HSSFWorkbook wb = new HSSFWorkbook(); @@ -674,16 +744,16 @@ public final class TestHSSFSheet extends BaseTestSheet { //TODO: check shapeId in the cloned sheet } - + /** * POI now (Sep 2008) allows sheet names longer than 31 chars (for other apps besides Excel). * Since Excel silently truncates to 31, make sure that POI enforces uniqueness on the first - * 31 chars. + * 31 chars. */ public void testLongSheetNames() { HSSFWorkbook wb = new HSSFWorkbook(); final String SAME_PREFIX = "A123456789B123456789C123456789"; // 30 chars - + wb.createSheet(SAME_PREFIX + "Dxxxx"); try { wb.createSheet(SAME_PREFIX + "Dyyyy"); // identical up to the 32nd char @@ -693,7 +763,7 @@ public final class TestHSSFSheet extends BaseTestSheet { } wb.createSheet(SAME_PREFIX + "Exxxx"); // OK - differs in the 31st char } - + /** * Tests that we can read existing column styles */ @@ -701,17 +771,17 @@ public final class TestHSSFSheet extends BaseTestSheet { HSSFWorkbook wbNone = HSSFTestDataSamples.openSampleWorkbook("ColumnStyleNone.xls"); HSSFWorkbook wbSimple = HSSFTestDataSamples.openSampleWorkbook("ColumnStyle1dp.xls"); HSSFWorkbook wbComplex = HSSFTestDataSamples.openSampleWorkbook("ColumnStyle1dpColoured.xls"); - + // Presence / absence checks assertNull(wbNone.getSheetAt(0).getColumnStyle(0)); assertNull(wbNone.getSheetAt(0).getColumnStyle(1)); - + assertNull(wbSimple.getSheetAt(0).getColumnStyle(0)); assertNotNull(wbSimple.getSheetAt(0).getColumnStyle(1)); - + assertNull(wbComplex.getSheetAt(0).getColumnStyle(0)); assertNotNull(wbComplex.getSheetAt(0).getColumnStyle(1)); - + // Details checks HSSFCellStyle bs = wbSimple.getSheetAt(0).getColumnStyle(1); assertEquals(62, bs.getIndex()); @@ -721,8 +791,8 @@ public final class TestHSSFSheet extends BaseTestSheet { assertEquals(8, bs.getFont(wbSimple).getColor()); assertFalse(bs.getFont(wbSimple).getItalic()); assertEquals(HSSFFont.BOLDWEIGHT_NORMAL, bs.getFont(wbSimple).getBoldweight()); - - + + HSSFCellStyle cs = wbComplex.getSheetAt(0).getColumnStyle(1); assertEquals(62, cs.getIndex()); assertEquals("#,##0.0_ ;\\-#,##0.0\\ ", cs.getDataFormatString()); @@ -732,5 +802,4 @@ public final class TestHSSFSheet extends BaseTestSheet { assertFalse(cs.getFont(wbComplex).getItalic()); assertEquals(HSSFFont.BOLDWEIGHT_BOLD, cs.getFont(wbComplex).getBoldweight()); } - }