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
This commit is contained in:
Josh Micich 2009-06-12 19:17:59 +00:00
parent f14b64759d
commit 635c570598
8 changed files with 502 additions and 258 deletions

View File

@ -35,6 +35,7 @@
<release version="3.5-beta7" date="2009-??-??">
</release>
<release version="3.5-beta6" date="2009-06-22">
<action dev="POI-DEVELOPERS" type="fix">47363 - Fixed HSSFSheet to allow addition of data validations after sheet protection</action>
<action dev="POI-DEVELOPERS" type="fix">47294 - Fixed XSSFWorkbook#setRepeatingRowsAndColumns to tolerate sheet names with quotes</action>
<action dev="POI-DEVELOPERS" type="fix">47309 - Fixed logic in HSSFCell.getCellComment to handle sheets with more than 65536 comments</action>
<action dev="POI-DEVELOPERS" type="fix">46776 - Added clone() method to MulBlankRecord to fix crash in Sheet.cloneSheet()</action>

View File

@ -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<p/>
*
*
* 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<RecordBase> 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
* <pre>
* + 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
* </pre>
*/
private static int getWorksheetProtectionBlockInsertPos(List<RecordBase> 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:
* <pre>
* o DEFCOLWIDTH
* oo COLINFO
* o SORT
* </pre>
*/
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<RecordBase> 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 <code>true</code> 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 <code>true</code> if the specified record id normally appears in the row blocks section
* @return <code>true</code> 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:

View File

@ -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'<br/>
* 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.
* @return the {@link WorksheetProtectionBlock} for this sheet
*/
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;
public WorksheetProtectionBlock getProtectionBlock() {
return _protectionBlock;
}
/**
* 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.
*/
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;
}
/**
* 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)
{

View File

@ -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, <code>null</code> 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 <code>true</code> 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:

View File

@ -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.
* <p/>
*
* 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 <code>true</code> 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.
* <p/>
* 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.
*
* <p/>
* <b>Note</b> - 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 <code>null</code> 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();
}
}

View File

@ -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];
return getProtectionBlock().isScenarioProtected();
}
/**
* 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);
}
/**
* Sets the protection enabled as well as the password
* @param password to set for protection
* @param password to set for protection. Pass <code>null</code> 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<EscherRecord> 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;
}

View File

@ -644,15 +644,14 @@ public final class TestSheet extends TestCase {
Sheet sheet = Sheet.createSheet();
List<RecordBase> 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

View File

@ -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();
hssfSheet.protectSheet(passwordA);
assertFalse(protect.getProtect());
assertEquals(expectedHashA, hssfSheet.getSheet().getProtectionBlock().getPasswordHash());
// 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());
// 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<? extends Record> 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());
}
}