<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>
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;
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() {
}
/**
* 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());
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;
if (rb instanceof DataValidityTable) {
continue;
}
-
+
Record rec = (Record) rb;
switch (rec.getSid()) {
case WindowTwoRecord.sid:
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
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:
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
}
/**
* @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) {
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");
}
}
/**
- * @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:
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;
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;
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;
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);
{
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;
_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");
}
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);
}
/**
- * 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
}
}
- /**
- * 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)
{
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;
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;
* @param data the data
*/
public UnknownRecord(int id, byte[] data) {
- _sid = id & 0xFFFF;
- _rawData = data;
+ _sid = id & 0xFFFF;
+ _rawData = data;
}
/**
* 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";
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
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";
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";
}
/**
- *
* @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
// 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:
--- /dev/null
+/* ====================================================================
+ 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();
+ }
+}
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;
* @return the parent workbook
*/
public HSSFWorkbook getWorkbook(){
- return _workbook;
+ return _workbook;
}
/**
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.
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 ",
* 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() {
{
_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);
}
*/
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());
}
/**
* @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);
}
/**
_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();
}
/**
* @return true => protection enabled; false => protection disabled
*/
public boolean getObjectProtect() {
- return getSheet().isProtected()[1];
+ return getProtectionBlock().isObjectProtected();
}
/**
* @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 <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)
}
/**
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.
* @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) {
}
NoteRecord[] noteRecs;
if (moveComments) {
- noteRecs = _sheet.getNoteRecords();
+ noteRecs = _sheet.getNoteRecords();
} else {
noteRecs = NoteRecord.EMPTY_ARRAY;
}
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();
}
// 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
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();
}
EscherAggregate agg = (EscherAggregate) _sheet.findFirstRecordBySid(EscherAggregate.sid);
return agg;
}
-
+
/**
* Returns the top-level drawing patriach, if there is
* one.
public HSSFPatriarch getDrawingPatriarch() {
EscherAggregate agg = getDrawingEscherAggregate();
if(agg == null) return null;
-
+
HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
agg.setPatriarch(patriarch);
public void autoSizeColumn(int column) {
autoSizeColumn(column, false);
}
-
+
/**
* Adjusts the column width to fit the contents.
*
* 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
*/
* '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);
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());
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) {
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;
}
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
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;
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());
/**
* 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<? 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();
//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
}
wb.createSheet(SAME_PREFIX + "Exxxx"); // OK - differs in the 31st char
}
-
+
/**
* Tests that we can read existing column styles
*/
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());
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());
assertFalse(cs.getFont(wbComplex).getItalic());
assertEquals(HSSFFont.BOLDWEIGHT_BOLD, cs.getFont(wbComplex).getBoldweight());
}
-
}