fpspreadsheet: Add support of workbook, worksheet, and cell protection for xlsx (modified patch by shobits1, see http://forum.lazarus.freepascal.org/index.php/topic,36075.0.html). Add protection unit tests.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5783 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2017-03-04 21:29:12 +00:00
parent 351694ec67
commit b3110a90cc
7 changed files with 612 additions and 48 deletions

View File

@ -1225,6 +1225,7 @@ begin
P^.NumberFormat := AItem.NumberFormat; P^.NumberFormat := AItem.NumberFormat;
P^.NumberFormatStr := AItem.NumberFormatStr; P^.NumberFormatStr := AItem.NumberFormatStr;
P^.BiDiMode := AItem.BiDiMode; P^.BiDiMode := AItem.BiDiMode;
P^.Protection := AItem.Protection;
Result := inherited Add(P); Result := inherited Add(P);
end; end;
end; end;
@ -1346,6 +1347,8 @@ begin
if (uffBiDi in AItem.UsedFormattingFields) then if (uffBiDi in AItem.UsedFormattingFields) then
if (P^.BiDiMode <> AItem.BiDiMode) then continue; if (P^.BiDiMode <> AItem.BiDiMode) then continue;
if (P^.Protection <> AItem.Protection) then continue;
// If we arrive here then the format records match. // If we arrive here then the format records match.
exit; exit;
end; end;

View File

@ -31,13 +31,17 @@ type
TsWorkbook = class; TsWorkbook = class;
{@@ Worksheet user interface options: {@@ Worksheet user interface options:
@param soShowGridLines Show or hide the grid lines in the spreadsheet @param soShowGridLines Show or hide the grid lines in the spreadsheet
@param soShowHeaders Show or hide the column or row headers of the spreadsheet @param soShowHeaders Show or hide the column or row headers of the
@param soHasFrozenPanes If set a number of rows and columns of the spreadsheet spreadsheet
is fixed and does not scroll. The number is defined by @param soHasFrozenPanes If set a number of rows and columns of the
LeftPaneWidth and TopPaneHeight. spreadsheet is fixed and does not scroll. The number
@param soHidden Worksheet is hidden. } is defined by LeftPaneWidth and TopPaneHeight.
TsSheetOption = (soShowGridLines, soShowHeaders, soHasFrozenPanes, soHidden); @param soHidden Worksheet is hidden.
@param soProtected Worksheet is protected
@param soPanesProtection Panes are locked due to workbook protection }
TsSheetOption = (soShowGridLines, soShowHeaders, soHasFrozenPanes, soHidden,
soProtected, soPanesProtection);
{@@ Set of user interface options {@@ Set of user interface options
@ see TsSheetOption } @ see TsSheetOption }
@ -99,7 +103,9 @@ type
FDefaultRowHeight: Single; // in "character heights", i.e. line count FDefaultRowHeight: Single; // in "character heights", i.e. line count
FSortParams: TsSortParams; // Parameters of the current sorting operation FSortParams: TsSortParams; // Parameters of the current sorting operation
FBiDiMode: TsBiDiMode; FBiDiMode: TsBiDiMode;
FCryptoInfo: TsCryptoInfo;
FPageLayout: TsPageLayout; FPageLayout: TsPageLayout;
FProtection: TsWorksheetProtections;
FVirtualColCount: Cardinal; FVirtualColCount: Cardinal;
FVirtualRowCount: Cardinal; FVirtualRowCount: Cardinal;
FZoomFactor: Double; FZoomFactor: Double;
@ -197,6 +203,7 @@ type
function ReadVertAlignment(ACell: PCell): TsVertAlignment; function ReadVertAlignment(ACell: PCell): TsVertAlignment;
function ReadWordwrap(ACell: PCell): boolean; function ReadWordwrap(ACell: PCell): boolean;
function ReadBiDiMode(ACell: PCell): TsBiDiMode; function ReadBiDiMode(ACell: PCell): TsBiDiMode;
function ReadProtection(ACell: PCell): TsCellProtections;
function IsEmpty: Boolean; function IsEmpty: Boolean;
@ -375,6 +382,11 @@ type
function WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; overload; function WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; overload;
procedure WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); overload; procedure WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); overload;
function WriteCellProtection(ARow, ACol: Cardinal;
AValue: TsCellProtections): PCell; overload;
procedure WriteCellProtection(ACell: PCell;
AValue: TsCellProtections); overload;
{ Formulas } { Formulas }
function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula; function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula;
procedure CalcFormula(ACell: PCell); procedure CalcFormula(ACell: PCell);
@ -544,7 +556,11 @@ type
AOffsetX: Double = 0.0; AOffsetY: Double = 0.0; AScaleX: Double = 1.0; AOffsetX: Double = 0.0; AOffsetY: Double = 0.0; AScaleX: Double = 1.0;
AScaleY: Double = 1.0): Integer; overload; AScaleY: Double = 1.0): Integer; overload;
// Notification of changed cells, rows or columns { Protection }
procedure Protect(AEnable: Boolean);
function IsProtected: Boolean;
{ Notification of changed cells, rows or columns }
procedure ChangedCell(ARow, ACol: Cardinal); procedure ChangedCell(ARow, ACol: Cardinal);
procedure ChangedCol(ACol: Cardinal); procedure ChangedCol(ACol: Cardinal);
procedure ChangedFont(ARow, ACol: Cardinal); procedure ChangedFont(ARow, ACol: Cardinal);
@ -557,6 +573,8 @@ type
property Cells: TsCells read FCells; property Cells: TsCells read FCells;
{@@ List of all column records of the worksheet having a non-standard column width } {@@ List of all column records of the worksheet having a non-standard column width }
property Cols: TIndexedAVLTree read FCols; property Cols: TIndexedAVLTree read FCols;
{@@ Information how the worksheet is encrypted }
property CryptoInfo: TsCryptoInfo read FCryptoInfo write FCryptoInfo;
{@@ List of all comment records } {@@ List of all comment records }
property Comments: TsComments read FComments; property Comments: TsComments read FComments;
{@@ List of merged cells (contains TsCellRange records) } {@@ List of merged cells (contains TsCellRange records) }
@ -570,6 +588,8 @@ type
property Name: string read FName write SetName; property Name: string read FName write SetName;
{@@ Parameters to be used for printing by the Office applications } {@@ Parameters to be used for printing by the Office applications }
property PageLayout: TsPageLayout read FPageLayout write FPageLayout; property PageLayout: TsPageLayout read FPageLayout write FPageLayout;
{@@ Worksheet protection options }
property Protection: TsWorksheetProtections read FProtection write FProtection;
{@@ List of all row records of the worksheet having a non-standard row height } {@@ List of all row records of the worksheet having a non-standard row height }
property Rows: TIndexedAVLTree read FRows; property Rows: TIndexedAVLTree read FRows;
{@@ Workbook to which the worksheet belongs } {@@ Workbook to which the worksheet belongs }
@ -698,6 +718,9 @@ type
FLog: TStringList; FLog: TStringList;
FSearchEngine: TObject; FSearchEngine: TObject;
FUnits: TsSizeUnits; FUnits: TsSizeUnits;
FProtection: TsWorkbookProtections;
FCryptoInfo: TsCryptoInfo;
{FrevisionsCrypto: TsCryptoInfo;} // Commented out because it needs revision handling
{ Setter/Getter } { Setter/Getter }
function GetErrorMsg: String; function GetErrorMsg: String;
@ -841,6 +864,9 @@ type
procedure UpdateCaches; procedure UpdateCaches;
procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal); procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal);
{ Protection }
function IsProtected: Boolean;
{ Error messages } { Error messages }
procedure AddErrorMsg(const AMsg: String); overload; procedure AddErrorMsg(const AMsg: String); overload;
procedure AddErrorMsg(const AMsg: String; const Args: array of const); overload; procedure AddErrorMsg(const AMsg: String; const Args: array of const); overload;
@ -848,6 +874,7 @@ type
{@@ Identifies the "active" worksheet (only for visual controls)} {@@ Identifies the "active" worksheet (only for visual controls)}
property ActiveWorksheet: TsWorksheet read FActiveWorksheet write SelectWorksheet; property ActiveWorksheet: TsWorksheet read FActiveWorksheet write SelectWorksheet;
property CryptoInfo: TsCryptoInfo read FCryptoInfo write FCryptoInfo;
{@@ Retrieves error messages collected during reading/writing } {@@ Retrieves error messages collected during reading/writing }
property ErrorMsg: String read GetErrorMsg; property ErrorMsg: String read GetErrorMsg;
{@@ Filename of the saved workbook } {@@ Filename of the saved workbook }
@ -855,6 +882,8 @@ type
{@@ Identifies the file format which was detected when reading the file } {@@ Identifies the file format which was detected when reading the file }
property FileFormatID: TsSpreadFormatID read FFormatID; property FileFormatID: TsSpreadFormatID read FFormatID;
property Options: TsWorkbookOptions read FOptions write FOptions; property Options: TsWorkbookOptions read FOptions write FOptions;
{property RevisionsCrypto: TsCryptoInfo read FRevisionsCrypto write FRevisionsCrypto;}
property Protection: TsWorkbookProtections read FProtection write FProtection;
property Units: TsSizeUnits read FUnits; property Units: TsSizeUnits read FUnits;
{@@ This event fires whenever a new worksheet is added } {@@ This event fires whenever a new worksheet is added }
@ -1160,6 +1189,8 @@ begin
FActiveCellRow := UNASSIGNED_ROW_COL_INDEX; FActiveCellRow := UNASSIGNED_ROW_COL_INDEX;
FActiveCellCol := UNASSIGNED_ROW_COL_INDEX; FActiveCellCol := UNASSIGNED_ROW_COL_INDEX;
FProtection := DEFAULT_SHEET_PROTECTIONS;
FOptions := [soShowGridLines, soShowHeaders]; FOptions := [soShowGridLines, soShowHeaders];
end; end;
@ -3481,6 +3512,24 @@ begin
end; end;
end; end;
{@@ ----------------------------------------------------------------------------
Returns the protection flags of the cell.
NOTE: These flags are active only if sheet protection is active, i.e.
spCells in Worksheet.Protection.
-------------------------------------------------------------------------------}
function TsWorksheet.ReadProtection(ACell: PCell): TsCellProtections;
var
fmt: PsCellFormat;
begin
Result := DEFAULT_CELL_PROTECTION;
if (ACell <> nil) then
begin
fmt := Workbook.GetPointerToCellFormat(ACell^.FormatIndex);
if fmt <> nil then
Result := fmt^.Protection;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Returns true if the worksheet does not contain any cell, column or row records Returns true if the worksheet does not contain any cell, column or row records
@ -4042,6 +4091,25 @@ begin
FWorkbook.FOnChangeWorksheet(FWorkbook, self); FWorkbook.FOnChangeWorksheet(FWorkbook, self);
end; end;
{@@ ----------------------------------------------------------------------------
Enables (or disables) protection of the worksheet. Details of protection are
specified in the set of Sheetprotection options
-------------------------------------------------------------------------------}
procedure TsWorksheet.Protect(AEnable: Boolean);
begin
if AEnable then
Include(FOptions, soProtected) else
Exclude(FOptions, soProtected);
end;
{@@ ----------------------------------------------------------------------------
Returns whether the worksheet is protected
-------------------------------------------------------------------------------}
function TsWorksheet.IsProtected: Boolean;
begin
Result := soProtected in FOptions;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Setter for the worksheet name property. Checks if the name is valid, and Setter for the worksheet name property. Checks if the name is valid, and
exits without any change if not. Creates an event OnChangeWorksheet. exits without any change if not. Creates an event OnChangeWorksheet.
@ -6675,7 +6743,7 @@ end;
function TsWorksheet.WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; function TsWorksheet.WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell;
begin begin
Result := GetCell(ARow, ACol); Result := GetCell(ARow, ACol);
WriteBiDiMode(Result, AVAlue); WriteBiDiMode(Result, AValue);
end; end;
procedure TsWorksheet.WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); procedure TsWorksheet.WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode);
@ -6694,6 +6762,26 @@ begin
ChangedCell(ACell^.Row, ACell^.Col); ChangedCell(ACell^.Row, ACell^.Col);
end; end;
function TsWorksheet.WriteCellProtection(ARow, ACol: Cardinal;
AValue: TsCellProtections): PCell;
begin
Result := GetCell(ARow, ACol);
WriteCellProtection(Result, AValue);
end;
procedure TsWorksheet.WriteCellProtection(ACell: PCell;
AValue: TsCellProtections);
var
fmt: TsCellFormat;
begin
if ACell = nil then
exit;
fmt := Workbook.GetCellFormat(ACell^.FormatIndex);
fmt.Protection := AValue;
ACell^.FormatIndex := Workbook.AddCellFormat(fmt);
ChangedCell(ACell^.Row, ACell^.Col);
end;
function TsWorksheet.GetDefaultColWidth: Single; function TsWorksheet.GetDefaultColWidth: Single;
begin begin
Result := ReadDefaultColWidth(suChars); Result := ReadDefaultColWidth(suChars);
@ -8042,6 +8130,13 @@ begin
end; end;
end; end;
{@@ ----------------------------------------------------------------------------
Returns whether the workbook is protected
-------------------------------------------------------------------------------}
function TsWorkbook.IsProtected: Boolean;
begin
Result := (FProtection <> []);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads the document from a file. It is assumed to have the given file format. Reads the document from a file. It is assumed to have the given file format.

View File

@ -638,6 +638,45 @@ type
{@@ Switch a cell from left-to-right to right-to-left orientation } {@@ Switch a cell from left-to-right to right-to-left orientation }
TsBiDiMode = (bdDefault, bdLTR, bdRTL); TsBiDiMode = (bdDefault, bdLTR, bdRTL);
{@@ }
TsCryptoInfo = record
AlgorithmName: string;
Password: string; // For old version of Excel (2010 and earlier)
HashValue: string;
SaltValue: string;
SpinCount: Integer;
end;
{@@ Workbook protection options }
TsWorkbookProtection = (bpLockRevision, bpLockStructure, bpLockWindows);
TsWorkbookProtections = set of TsWorkbookProtection;
{@@ Worksheet protection options. All selected items are locked. }
TsWorksheetProtection = (
spFormatCells, spFormatColumns, spFormatRows,
spDeleteColumns, spDeleteRows,
spInsertColumns, spInsertRows, spInsertHyperlinks,
spCells, spSort,
spSelectLockedCells, spSelectUnlockedCells
{spObjects, spPivotTables, spScenarios }
);
TsWorksheetProtections = set of TsWorksheetProtection;
{@@ Cell protection options }
TsCellProtection = (cpLockCell, cpHideFormulas);
TsCellProtections = set of TsCellProtection;
const
ALL_SHEET_PROTECTIONS = [spFormatCells, spFormatColumns, spFormatRows,
spDeleteColumns, spDeleteRows, spInsertColumns, spInsertRows, spInsertHyperlinks,
spCells, spSort, spSelectLockedCells, spSelectUnlockedCells
{, spObjects, spPivotTables, spScenarios} ];
DEFAULT_SHEET_PROTECTIONS = ALL_SHEET_PROTECTIONS - [spSelectLockedCells, spSelectUnlockedcells];
DEFAULT_CELL_PROTECTION = [cpLockCell];
type
{@@ Record containing all details for cell formatting } {@@ Record containing all details for cell formatting }
TsCellFormat = record TsCellFormat = record
Name: String; Name: String;
@ -652,6 +691,7 @@ type
Background: TsFillPattern; Background: TsFillPattern;
NumberFormatIndex: Integer; NumberFormatIndex: Integer;
BiDiMode: TsBiDiMode; BiDiMode: TsBiDiMode;
Protection: TsCellProtections;
// next two are deprecated... // next two are deprecated...
NumberFormat: TsNumberFormat; NumberFormat: TsNumberFormat;
NumberFormatStr: String; NumberFormatStr: String;

View File

@ -170,6 +170,7 @@ procedure FixHyperlinkPathDelims(var ATarget: String);
procedure InitCell(out ACell: TCell); overload; procedure InitCell(out ACell: TCell); overload;
procedure InitCell(ARow, ACol: Cardinal; out ACell: TCell); overload; procedure InitCell(ARow, ACol: Cardinal; out ACell: TCell); overload;
procedure InitCryptoInfo(out AValue: TsCryptoInfo);
procedure InitFormatRecord(out AValue: TsCellFormat); procedure InitFormatRecord(out AValue: TsCellFormat);
procedure InitImageRecord(out AValue: TsImage; ARow, ACol: Cardinal; procedure InitImageRecord(out AValue: TsImage; ARow, ACol: Cardinal;
AOffsetX, AOffsetY, AScaleX, AScaleY: Double); AOffsetX, AOffsetY, AScaleX, AScaleY: Double);
@ -2079,6 +2080,18 @@ begin
ACell.Col := ACol; ACell.Col := ACol;
end; end;
{@@ ----------------------------------------------------------------------------
Initializes the fields of the encryption information block (TsCryptoInfo)
-------------------------------------------------------------------------------}
procedure InitCryptoInfo(out AValue: TsCryptoInfo);
begin
AValue.Password := '';
AValue.AlgorithmName := '';
AValue.HashValue := '';
AValue.SaltValue := '';
AValue.SpinCount := 0;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Initializes the fields of a TsCellFormaRecord Initializes the fields of a TsCellFormaRecord
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
@ -2089,7 +2102,10 @@ begin
FillChar(AValue, SizeOf(AValue), 0); FillChar(AValue, SizeOf(AValue), 0);
AValue.BorderStyles := DEFAULT_BORDERSTYLES; AValue.BorderStyles := DEFAULT_BORDERSTYLES;
AValue.Background := EMPTY_FILL; AValue.Background := EMPTY_FILL;
AValue.NumberFormatIndex := -1; // GENERAL format not contained in NumFormatList AValue.NumberFormatIndex := -1;
// GENERAL format not contained in NumFormatList
AValue.Protection := DEFAULT_CELL_PROTECTION;
// NOTE: Cell protection is effective only after protecting a worksheet
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------

View File

@ -93,9 +93,11 @@ type
procedure ReadSharedStrings(ANode: TDOMNode); procedure ReadSharedStrings(ANode: TDOMNode);
procedure ReadSheetFormatPr(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadSheetFormatPr(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadSheetList(ANode: TDOMNode); procedure ReadSheetList(ANode: TDOMNode);
procedure ReadSheetProtection(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadThemeElements(ANode: TDOMNode); procedure ReadThemeElements(ANode: TDOMNode);
procedure ReadThemeColors(ANode: TDOMNode); procedure ReadThemeColors(ANode: TDOMNode);
procedure ReadWorkbookProtection(ANode: TDOMNode);
procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet);
protected protected
FFirstNumFormatIndexInFile: Integer; FFirstNumFormatIndexInFile: Integer;
@ -118,6 +120,7 @@ type
FSharedStringsCount: Integer; FSharedStringsCount: Integer;
FFillList: array of PsCellFormat; FFillList: array of PsCellFormat;
FBorderList: array of PsCellFormat; FBorderList: array of PsCellFormat;
function GetActiveTab: String;
procedure Get_rId(AWorksheet: TsWorksheet; procedure Get_rId(AWorksheet: TsWorksheet;
out AComment_rId, AFirstHyperlink_rId, ADrawing_rId, ADrawingHF_rId: Integer); out AComment_rId, AFirstHyperlink_rId, ADrawing_rId, ADrawingHF_rId: Integer);
protected protected
@ -153,6 +156,8 @@ type
procedure WriteSheetData(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheetData(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteSheetFormatPr(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheetFormatPr(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteSheetPr(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheetPr(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteSheetProtection(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteSheets(AStream: TStream);
procedure WriteSheetViews(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheetViews(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteStyleList(AStream: TStream; ANodeName: String); procedure WriteStyleList(AStream: TStream; ANodeName: String);
procedure WriteVmlDrawings(AWorksheet: TsWorksheet); procedure WriteVmlDrawings(AWorksheet: TsWorksheet);
@ -160,6 +165,7 @@ type
procedure WriteVMLDrawings_HeaderFooterImages(AWorksheet: TsWorksheet); procedure WriteVMLDrawings_HeaderFooterImages(AWorksheet: TsWorksheet);
procedure WriteVMLDrawingRels(AWorksheet: TsWorksheet); procedure WriteVMLDrawingRels(AWorksheet: TsWorksheet);
procedure WriteWorkbook(AStream: TStream); procedure WriteWorkbook(AStream: TStream);
procedure WriteWorkbookProtection(AStream: TStream);
procedure WriteWorkbookRels(AStream: TStream); procedure WriteWorkbookRels(AStream: TStream);
procedure WriteWorksheet(AWorksheet: TsWorksheet); procedure WriteWorksheet(AWorksheet: TsWorksheet);
procedure WriteWorksheetRels(AWorksheet: TsWorksheet); procedure WriteWorksheetRels(AWorksheet: TsWorksheet);
@ -802,6 +808,7 @@ var
fillData: TFillListData; fillData: TFillListData;
borderData: TBorderListData; borderData: TBorderListData;
fnt: TsFont; fnt: TsFont;
cp: TsCellProtections;
begin begin
node := ANode.FirstChild; node := ANode.FirstChild;
while Assigned(node) do while Assigned(node) do
@ -934,6 +941,29 @@ begin
childNode := childNode.NextSibling; childNode := childNode.NextSibling;
end; end;
end; end;
// protection
s2 := GetAttrValue(node, 'applyProtection');
if (s2 <> '0') and (s2 <> '') then
begin
cp := [cpLockCell];
childNode := node.FirstChild;
while Assigned(childNode) do begin
nodeName := childNode.NodeName;
if nodeName = 'protection' then
begin
s1 := GetAttrValue(childNode, 'locked');
if (s1 = '0') then
Exclude(cp, cpLockCell);
s1 := GetAttrValue(childNode, 'hidden');
if (s1= '1') then
Include(cp, cpHideFormulas);
end;
childNode := childNode.NextSibling;
end;
fmt.Protection := cp;
end;
if fmt.FontIndex > 0 then if fmt.FontIndex > 0 then
Include(fmt.UsedFormattingFields, uffFont); Include(fmt.UsedFormattingFields, uffFont);
if fmt.Border <> [] then if fmt.Border <> [] then
@ -1971,6 +2001,135 @@ begin
end; end;
end; end;
procedure TsSpreadOOXMLReader.ReadSheetProtection(ANode: TDOMNode;
AWorksheet: TsWorksheet);
var
s: String;
shc: TsCryptoInfo;
shp0, shp1: TsWorksheetProtections;
begin
if ANode = nil then
exit;
InitCryptoInfo(shc);
s := GetAttrValue(ANode, 'password');
if s <> '' then
shc.Password := s
else
begin
s := GetAttrValue(ANode, 'hashValue');
if s <> '' then begin
shc.HashValue := s;
s := GetAttrValue(ANode, 'algorithmName');
shc.AlgorithmName := s;
s := GetAttrValue(ANode, 'saltValue');
shc.SaltValue := s;
s := GetAttrValue(ANode, 'spinCount');
shc.SpinCount := StrToIntDef(s, 0);
end;
end;
AWorksheet.CryptoInfo := shc;
shp1 := []; // will get "1" to include
shp0 := ALL_SHEET_PROTECTIONS; // will get "0" to exclude
// Attribute not found -> property = false
s := GetAttrValue(ANode, 'sheet');
if (s = '1') then
Include(shp1, spCells) else
Exclude(shp0, spCells);
s := GetAttrValue(ANode, 'selectLockedCells');
if (s = '1') then
Include(shp1, spSelectLockedCells) else
Exclude(shp0, spSelectLockedCells);
s := GetAttrValue(ANode, 'selectUnlockedCells');
if (s = '1') then
Include(shp1, spSelectUnlockedCells) else
Exclude(shp0, spSelectUnlockedCells);
// these options are currently not supported by fpspreadsheet
{
s := GetAttrValue(ANode, 'objects');
if (s = '1') then
Include(shp1, spObjects) else
Exclude(shp0, spObjects);
s := GetAttrValue(ANode, 'scenarios');
if (s = '1') then
Include(shp1, spScenarios) else
Exclude(shp0, spScenarios);
}
// Attribute not found -> property = true
{
s := GetAttrValue(ANode, 'autoFilter');
if (s = '0') then
Exclude(shp1, spAutoFilter) else
Include(shp0, spAutoFilter);
}
s := GetAttrValue(ANode, 'deleteColumns');
if (s = '0') then
Exclude(shp0, spDeleteColumns) else
Include(shp1, spDeleteColumns);
s := GetAttrValue(ANode, 'deleteRows');
if (s = '0') then
Exclude(shp0, spDeleteRows) else
Include(shp1, spDeleteRows);
s := GetAttrValue(ANode, 'formatCells');
if (s = '0') then
Exclude(shp0, spFormatCells) else
Include(shp1, spFormatCells);
s := GetAttrValue(ANode, 'formatColumns');
if (s = '0') then
Exclude(shp0, spFormatColumns) else
Include(shp1, spFormatColumns);
s := GetAttrValue(ANode, 'formatRows');
if (s = '0') then
Exclude(shp0, spFormatRows) else
Include(shp1, spFormatRows);
s := GetAttrValue(ANode, 'insertColumns');
if (s = '0') then
Exclude(shp0, spInsertColumns) else
Include(shp1, spInsertColumns);
s := GetAttrValue(ANode, 'insertHyperlinks');
if (s = '0') then
Exclude(shp0, spInsertHyperlinks) else
Include(shp1, spInsertHyperlinks);
s := GetAttrValue(ANode, 'insertRows');
if (s = '0') then
Exclude(shp0, spInsertRows) else
Include(shp1, spInsertRows);
s := GetAttrValue(ANode, 'sort');
if (s = '0') then
Exclude(shp0, spSort) else
Include(shp1, spSort);
// Currently no pivottable support in fpspreadsheet
{
s := GetAttrValue(ANode, 'pivotTables');
if (s = '0') then
Exclude(shp0, spPivotTables) else
Include(shp1, spPivotTables);
}
AWorksheet.Protection := shp0 + shp1;
AWorksheet.Protect(true);
end;
procedure TsSpreadOOXMLReader.ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure TsSpreadOOXMLReader.ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet);
var var
sheetViewNode: TDOMNode; sheetViewNode: TDOMNode;
@ -1994,6 +2153,14 @@ begin
if s = '0' then if s = '0' then
AWorksheet.Options := AWorksheet.Options - [soShowHeaders]; AWorksheet.Options := AWorksheet.Options - [soShowHeaders];
s := GetAttrValue(sheetViewNode, 'tabSelected');
if s = '1' then
Workbook.ActiveWorksheet := AWorksheet;
s := GetAttrValue(sheetViewNode, 'windowProtection');
if s = '1' then
AWorksheet.Options := AWorksheet.Options + [soPanesProtection];
s := GetAttrValue(sheetViewNode, 'zoomScale'); s := GetAttrValue(sheetViewNode, 'zoomScale');
if s <> '' then if s <> '' then
AWorksheet.ZoomFactor := StrToFloat(s, FPointSeparatorSettings) * 0.01; AWorksheet.ZoomFactor := StrToFloat(s, FPointSeparatorSettings) * 0.01;
@ -2108,6 +2275,66 @@ begin
end; end;
end; end;
procedure TsSpreadOOXMLReader.ReadWorkbookProtection(ANode: TDOMNode);
var
s : string;
wbc: TsCryptoInfo;
wbp: TsWorkbookProtections;
begin
s := '';
wbp := [];
if ANode = nil then
Exit;
InitCryptoInfo(wbc);
s := GetAttrValue(ANode, 'workbookPassword');
if s <> '' then
wbc.Password := s
else
begin
s := GetAttrValue(ANode, 'workbookHashVal');
if s <> '' then begin
wbc.HashValue := s;
wbc.AlgorithmName := GetAttrValue(ANode, 'workbookAlgorithmName');
wbc.SaltValue := GetAttrValue(ANode, 'workbookSaltValue');
wbc.SpinCount := StrToIntDef(GetAttrValue(ANode, 'workbookSpinCount'), 0);
end;
end;
Workbook.CryptoInfo := wbc;
{
InitCryptoInfo(wbc);
s := GetAttrValue(ANode, 'revisionsPassword');
if s <> '' then
wbc.Password := s
else
begin
s := GetAttrValue(ANode, 'revisionsHashValue');
if s <> '' then begin
wbc.HashValue := s;
wbc.AlgorithmName := GetAttrValue(ANode, 'revisionsAlgorithm');
wbc.SaltValue := GetAttrValue(ANode, 'revisionsSaltValue');
wbc.SpinCount := StrToIntDef(GetAttrValue(ANode, 'revisionsSpinCount'), 0);
end;
end;
Workbook.RevisionsCrypto := wbc;
}
s := GetAttrValue(ANode, 'lockStructure');
if (s = '1') then
Include(wbp, bpLockStructure);
s := GetAttrValue(ANode, 'lockWindows');
if (s = '1') then
Include(wbp, bpLockWindows);
s := GetAttrValue(ANode, 'lockRevision');
if (s = '1') then
Include(wbp, bpLockRevision);
Workbook.Protection := wbp;
end;
procedure TsSpreadOOXMLReader.ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure TsSpreadOOXMLReader.ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet);
var var
rownode: TDOMNode; rownode: TDOMNode;
@ -2178,6 +2405,7 @@ begin
ReadXMLStream(Doc, XMLStream); ReadXMLStream(Doc, XMLStream);
ReadFileVersion(Doc.DocumentElement.FindNode('fileVersion')); ReadFileVersion(Doc.DocumentElement.FindNode('fileVersion'));
ReadDateMode(Doc.DocumentElement.FindNode('workbookPr')); ReadDateMode(Doc.DocumentElement.FindNode('workbookPr'));
ReadWorkbookProtection(Doc.DocumentElement.FindNode('workbookProtection'));
ReadSheetList(Doc.DocumentElement.FindNode('sheets')); ReadSheetList(Doc.DocumentElement.FindNode('sheets'));
//ReadDefinedNames(Doc.DocumentElement.FindNode('definedNames')); -- don't read here because sheets do not yet exist //ReadDefinedNames(Doc.DocumentElement.FindNode('definedNames')); -- don't read here because sheets do not yet exist
ReadActiveSheet(Doc.DocumentElement.FindNode('bookViews'), actSheetIndex); ReadActiveSheet(Doc.DocumentElement.FindNode('bookViews'), actSheetIndex);
@ -2248,6 +2476,7 @@ begin
ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet); ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet);
ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet); ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet);
ReadWorksheet(Doc.DocumentElement.FindNode('sheetData'), FWorksheet); ReadWorksheet(Doc.DocumentElement.FindNode('sheetData'), FWorksheet);
ReadSheetProtection(Doc.DocumentElement.FindNode('sheetProtection'), FWorksheet);
ReadMergedCells(Doc.DocumentElement.FindNode('mergeCells'), FWorksheet); ReadMergedCells(Doc.DocumentElement.FindNode('mergeCells'), FWorksheet);
ReadHyperlinks(Doc.DocumentElement.FindNode('hyperlinks')); ReadHyperlinks(Doc.DocumentElement.FindNode('hyperlinks'));
ReadPrintOptions(Doc.DocumentElement.FindNode('printOptions'), FWorksheet); ReadPrintOptions(Doc.DocumentElement.FindNode('printOptions'), FWorksheet);
@ -2469,6 +2698,12 @@ begin
AFirstHyperlink_rId := next_rId; AFirstHyperlink_rId := next_rId;
end; end;
function TsSpreadOOXMLWriter.GetActiveTab: String;
begin
Result := IfThen(FWorkbook.ActiveWorksheet = nil, '',
' activeTab="' + IntToStr(FWorkbook.GetWorksheetIndex(FWorkbook.ActiveWorksheet)) + '"');
end;
{ Determines the formatting index which a given cell has in list of { Determines the formatting index which a given cell has in list of
"FormattingStyles" which correspond to the section cellXfs of the styles.xml "FormattingStyles" which correspond to the section cellXfs of the styles.xml
file. } file. }
@ -3187,6 +3422,113 @@ begin
'<sheetPr>' + s + '</sheetPr>'); '<sheetPr>' + s + '</sheetPr>');
end; end;
procedure TsSpreadOOXMLWriter.WriteSheetProtection(AStream: TStream;
AWorksheet: TsWorksheet);
var
s: String;
begin
s := '';
// No attribute -> attr="0"
if AWorksheet.IsProtected then
s := ' sheet="1" objects="1" scenarios="1"'
else
Exit; //exit if sheet not protected
if AWorksheet.CryptoInfo.Password <> '' then
s := s + ' password="' + AWorksheet.CryptoInfo.Password + '"'
else
if AWorksheet.CryptoInfo.HashValue <> '' then
begin
s := s + ' hashValue="' + AWorksheet.CryptoInfo.HashValue + '"';
if AWorksheet.CryptoInfo.AlgorithmName <> '' then
s := s + ' algorithmName="' + AWorksheet.CryptoInfo.AlgorithmName + '"';
if AWorksheet.CryptoInfo.SaltValue <> '' then
s := s + ' saltValue="' + AWorksheet.CryptoInfo.SaltValue + '"';
if AWorksheet.CryptoInfo.SpinCount <> 0 then
s := s + ' spinCount="' + IntToStr(AWorksheet.CryptoInfo.SpinCount) + '"';
end;
{
if spObjects in AWorksheet.Protection then // to do: Remove from default above
s := s + ' objects="1"';
if spScenarios in AWorksheet.Protection then
s := s + ' scenarios="1"';
}
if spSelectLockedCells in AWorksheet.Protection then
s := s + ' selectLockedCells="1"';
if spSelectUnlockedCells in AWorksheet.Protection then
s := s + ' selectUnlockedCells="1"';
// No attribute -> attr="1"
{
if not (spAutoFilter in AWorksheet.Protection) then
s := s + ' autoFilter="0"';
}
if not (spDeleteColumns in AWorksheet.Protection) then
s := s + ' deleteColumns="0"';
if not (spDeleteRows in AWorksheet.Protection) then
s := s + ' deleteRows="0"';
if not (spFormatCells in AWorksheet.Protection) then
s := s + ' formatCells="0"';
if not (spFormatColumns in AWorksheet.Protection) then
s := s + ' formatColumns="0"';
if not (spFormatRows in AWorksheet.Protection) then
s := s + ' formatRows="0"';
if not (spInsertColumns in AWorksheet.Protection) then
s := s + ' insertColumns="0"';
if not (spInsertHyperlinks in AWorksheet.Protection) then
s := s + ' insertHyperlinks="0"';
if not (spInsertRows in AWorksheet.Protection) then
s := s + ' insertRows="0"';
{
if not (spPivotTables in AWorksheet.Protection) then
s := s + ' pivotTables="0"';
}
if not (spSort in AWorksheet.Protection) then
s := s + ' sort="0"';
if s <> '' then
AppendToStream(AStream,
'<sheetProtection' + s + ' />');
end;
procedure TsSpreadOOXMLWriter.WriteSheets(AStream: TStream);
var
counter: Integer;
sheet: TsWorksheet;
sheetName: String;
sheetState: String;
begin
AppendToStream(AStream,
'<sheets>');
for counter := 1 to Workbook.GetWorksheetCount do
begin
sheet := Workbook.GetWorksheetByIndex(counter-1);
sheetname := UTF8TextToXMLText(sheet.Name);
sheetState := IfThen(soHidden in sheet.Options, ' state="hidden"', '');
AppendToStream(AStream, Format(
'<sheet name="%s" sheetId="%d" r:id="rId%d"%s />',
[sheetname, counter, counter, sheetstate]));
end;
AppendToStream(AStream,
'</sheets>');
end;
procedure TsSpreadOOXMLWriter.WriteSheetViews(AStream: TStream; procedure TsSpreadOOXMLWriter.WriteSheetViews(AStream: TStream;
AWorksheet: TsWorksheet); AWorksheet: TsWorksheet);
const const
@ -3202,6 +3544,7 @@ var
bidi: String; bidi: String;
zoomscale: String; zoomscale: String;
attr: String; attr: String;
windowProtection: String;
begin begin
// Show gridlines ? // Show gridlines ?
showGridLines := StrUtils.IfThen(soShowGridLines in AWorksheet.Options, '', ' showGridLines="0"'); showGridLines := StrUtils.IfThen(soShowGridLines in AWorksheet.Options, '', ' showGridLines="0"');
@ -3231,8 +3574,14 @@ begin
// Selected tab? // Selected tab?
tabSel := StrUtils.IfThen(AWorksheet = FWorkbook.ActiveWorksheet, ' tabSelected="1"', ''); tabSel := StrUtils.IfThen(AWorksheet = FWorkbook.ActiveWorksheet, ' tabSelected="1"', '');
// SheetView attributes // Window protection
attr := showGridLines + showHeaders + tabSel + zoomScale + bidi; if (soPanesProtection in AWorksheet.Options) and FWorkbook.IsProtected then
windowProtection := ' windowProtection="1"'
else
windowProtection := '';
// All SheetView attributes
attr := windowProtection + showGridLines + showHeaders + tabSel + zoomScale + bidi;
// No frozen panes // No frozen panes
if not (soHasFrozenPanes in AWorksheet.Options) or if not (soHasFrozenPanes in AWorksheet.Options) or
@ -3312,8 +3661,7 @@ end;
{ Writes the style list which the workbook has collected in its FormatList } { Writes the style list which the workbook has collected in its FormatList }
procedure TsSpreadOOXMLWriter.WriteStyleList(AStream: TStream; ANodeName: String); procedure TsSpreadOOXMLWriter.WriteStyleList(AStream: TStream; ANodeName: String);
var var
// styleCell: TCell; s, sAlign, sProtected: String;
s, sAlign: String;
fontID: Integer; fontID: Integer;
numFmtParams: TsNumFormatParams; numFmtParams: TsNumFormatParams;
numFmtStr: String; numFmtStr: String;
@ -3331,6 +3679,7 @@ begin
fmt := FWorkbook.GetPointerToCellFormat(i); fmt := FWorkbook.GetPointerToCellFormat(i);
s := ''; s := '';
sAlign := ''; sAlign := '';
sProtected := '';
{ Number format } { Number format }
if (uffNumberFormat in fmt^.UsedFormattingFields) then if (uffNumberFormat in fmt^.UsedFormattingFields) then
@ -3358,10 +3707,14 @@ begin
{ Text rotation } { Text rotation }
if (uffTextRotation in fmt^.UsedFormattingFields) then if (uffTextRotation in fmt^.UsedFormattingFields) then
case fmt^.TextRotation of case fmt^.TextRotation of
trHorizontal : ; trHorizontal:
rt90DegreeClockwiseRotation : sAlign := sAlign + Format('textRotation="%d" ', [180]); ;
rt90DegreeCounterClockwiseRotation: sAlign := sAlign + Format('textRotation="%d" ', [90]); rt90DegreeClockwiseRotation:
rtStacked : sAlign := sAlign + Format('textRotation="%d" ', [255]); sAlign := sAlign + Format('textRotation="%d" ', [180]);
rt90DegreeCounterClockwiseRotation:
sAlign := sAlign + Format('textRotation="%d" ', [90]);
rtStacked:
sAlign := sAlign + Format('textRotation="%d" ', [255]);
end; end;
{ Text alignment } { Text alignment }
@ -3389,6 +3742,12 @@ begin
if (uffBiDi in fmt^.UsedFormattingFields) and (fmt^.BiDiMode <> bdDefault) then if (uffBiDi in fmt^.UsedFormattingFields) and (fmt^.BiDiMode <> bdDefault) then
sAlign := sAlign + Format('readingOrder="%d" ', [Ord(fmt^.BiDiMode)]); sAlign := sAlign + Format('readingOrder="%d" ', [Ord(fmt^.BiDiMode)]);
if sAlign <> '' then
begin
s := s + 'applyAlignment="1" ';
sAlign := '<alignment ' + sAlign + '/>';
end;
{ Fill } { Fill }
if (uffBackground in fmt^.UsedFormattingFields) then if (uffBackground in fmt^.UsedFormattingFields) then
begin begin
@ -3405,14 +3764,27 @@ begin
s := s + Format('borderId="%d" applyBorder="1" ', [borderID]); s := s + Format('borderId="%d" applyBorder="1" ', [borderID]);
end; end;
{ Protection }
if not (cpLockCell in fmt^.Protection) then
sProtected := 'locked="0" ';
if (cpHideFormulas in fmt^.Protection) then
sProtected := sProtected + 'hidden="1" ';
if sProtected <> '' then
begin
s := s + 'applyProtection="1" ';
sProtected := '<protection ' + sProtected + '/>';
end;
{ Write everything to stream } { Write everything to stream }
if sAlign = '' then if (sAlign = '') and (sProtected = '') then
AppendToStream(AStream, AppendToStream(AStream,
'<xf ' + s + '/>') '<xf ' + s + '/>')
else else
AppendToStream(AStream, AppendToStream(AStream,
'<xf ' + s + 'applyAlignment="1">', '<xf ' + s + '>',
'<alignment ' + sAlign + ' />', sAlign + sProtected,
'</xf>'); '</xf>');
end; end;
@ -4273,16 +4645,7 @@ begin
end; end;
procedure TsSpreadOOXMLWriter.WriteWorkbook(AStream: TStream); procedure TsSpreadOOXMLWriter.WriteWorkbook(AStream: TStream);
var
actTab: String;
sheetName: String;
counter: Integer;
sheet: TsWorksheet;
sheetstate: String;
begin begin
actTab := IfThen(FWorkbook.ActiveWorksheet = nil, '',
'activeTab="' + IntToStr(FWorkbook.GetWorksheetIndex(FWorkbook.ActiveWorksheet)) + '"');
AppendToStream(AStream, AppendToStream(AStream,
XML_HEADER); XML_HEADER);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
@ -4291,25 +4654,13 @@ begin
'<fileVersion appName="fpspreadsheet" />'); '<fileVersion appName="fpspreadsheet" />');
AppendToStream(AStream, AppendToStream(AStream,
'<workbookPr defaultThemeVersion="124226" />'); '<workbookPr defaultThemeVersion="124226" />');
WriteWorkbookProtection(AStream);
AppendToStream(AStream, AppendToStream(AStream,
'<bookViews>' + '<bookViews>' +
'<workbookView xWindow="480" yWindow="90" windowWidth="15195" windowHeight="12525" ' + actTab + '/>' + '<workbookView xWindow="480" yWindow="90" ' +
'windowWidth="15195" windowHeight="12525"' + GetActiveTab + '/>' +
'</bookViews>'); '</bookViews>');
WriteSheets(AStream);
AppendToStream(AStream,
'<sheets>');
for counter:=1 to Workbook.GetWorksheetCount do
begin
sheet := Workbook.GetWorksheetByIndex(counter-1);
sheetname := UTF8TextToXMLText(sheet.Name);
sheetState := IfThen(soHidden in sheet.Options, ' state="hidden"', '');
AppendToStream(AStream, Format(
'<sheet name="%s" sheetId="%d" r:id="rId%d"%s />',
[sheetname, counter, counter, sheetstate]));
end;
AppendToStream(AStream,
'</sheets>');
WriteDefinedNames(AStream); WriteDefinedNames(AStream);
AppendToStream(AStream, AppendToStream(AStream,
@ -4318,6 +4669,60 @@ begin
'</workbook>'); '</workbook>');
end; end;
procedure TsSpreadOOXMLWriter.WriteWorkbookProtection(AStream: TStream);
var
s: String;
begin
s := '';
if Workbook.CryptoInfo.Password <> '' then
s := s + ' workbookPassword="' + Workbook.CryptoInfo.Password + '"'
else
if Workbook.CryptoInfo.HashValue <> '' then
begin
s:= s + ' workbookHashVal="' + Workbook.CryptoInfo.HashValue + '"';
if Workbook.CryptoInfo.AlgorithmName <> '' then
s:= s + ' workbookAlgorithmName="' + Workbook.CryptoInfo.AlgorithmName + '"';
if Workbook.CryptoInfo.SaltValue <> '' then
s:= s + ' workbookSaltValue="' + Workbook.CryptoInfo.SaltValue + '"';
if Workbook.CryptoInfo.SpinCount <> 0 then
s:= s + ' workbookSpinCount="' + IntToStr(Workbook.CryptoInfo.SpinCount) + '"';
end;
{
if Workbook.RevisionsCrypto.Password <> '' then
s:= s + ' revisionsPassword="' + Workbook.RevisionsCrypto.Password +'"'
else
if Workbook.RevisionsCrypto.HashValue <> '' then
begin
s:= s + ' revisionsHashValue="' + Workbook.RevisionsCrypto.HashValue +'"';
if Workbook.RevisionsCrypto.AlgorithmName <> '' then
s:= s + ' revisionsAlgorithm="' + Workbook.RevisionsCrypto.AlgorithmName +'"';
if Workbook.RevisionsCrypto.SaltValue <> '' then
s:= s + ' revisionsSaltValue="' + Workbook.RevisionsCrypto.SaltValue +'"';
if Workbook.RevisionsCrypto.SpinCount <> 0 then
s:= s + ' revisionsSpinCount="' + IntToStr( Workbook.RevisionsCrypto.SpinCount ) +'"';
end;
}
if bpLockStructure in Workbook.Protection then
s := s + ' lockStructure="1"';
if bpLockWindows in Workbook.Protection then
s := s + ' lockWindows="1"';
if bpLockRevision in Workbook.Protection then
s := s + ' lockRevision="1"';
if s <> '' then
AppendToStream(AStream, '<workbookProtection' + s +' />');
end;
procedure TsSpreadOOXMLWriter.WriteWorkbookRels(AStream: TStream); procedure TsSpreadOOXMLWriter.WriteWorkbookRels(AStream: TStream);
var var
counter: Integer; counter: Integer;
@ -4377,6 +4782,7 @@ begin
WriteSheetFormatPr(FSSheets[FCurSheetNum], AWorksheet); WriteSheetFormatPr(FSSheets[FCurSheetNum], AWorksheet);
WriteCols(FSSheets[FCurSheetNum], AWorksheet); WriteCols(FSSheets[FCurSheetNum], AWorksheet);
WriteSheetData(FSSheets[FCurSheetNum], AWorksheet); WriteSheetData(FSSheets[FCurSheetNum], AWorksheet);
WriteSheetProtection(FSSheets[FCurSheetNum], AWorksheet);
WriteMergedCells(FSSheets[FCurSheetNum], AWorksheet); WriteMergedCells(FSSheets[FCurSheetNum], AWorksheet);
WriteHyperlinks(FSSheets[FCurSheetNum], AWorksheet, rId_FirstHyperlink); WriteHyperlinks(FSSheets[FCurSheetNum], AWorksheet, rId_FirstHyperlink);

View File

@ -37,7 +37,7 @@
<PackageName Value="FCL"/> <PackageName Value="FCL"/>
</Item4> </Item4>
</RequiredPackages> </RequiredPackages>
<Units Count="26"> <Units Count="27">
<Unit0> <Unit0>
<Filename Value="spreadtestgui.lpr"/> <Filename Value="spreadtestgui.lpr"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
@ -143,6 +143,10 @@
<Filename Value="exceltests.pas"/> <Filename Value="exceltests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit25> </Unit25>
<Unit26>
<Filename Value="protectiontests.pas"/>
<IsPartOfProject Value="True"/>
</Unit26>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@ -13,7 +13,7 @@ uses
optiontests, numformatparsertests, formulatests, rpnFormulaUnit, exceltests, optiontests, numformatparsertests, formulatests, rpnFormulaUnit, exceltests,
emptycelltests, errortests, virtualmodetests, insertdeletetests, emptycelltests, errortests, virtualmodetests, insertdeletetests,
celltypetests, sortingtests, copytests, enumeratortests, commenttests, celltypetests, sortingtests, copytests, enumeratortests, commenttests,
hyperlinktests, pagelayouttests; hyperlinktests, pagelayouttests, protectiontests;
begin begin
{$IFDEF HEAPTRC} {$IFDEF HEAPTRC}