diff --git a/components/fpspreadsheet/source/common/fpsclasses.pas b/components/fpspreadsheet/source/common/fpsclasses.pas index 74a5ff695..eb7095e2d 100644 --- a/components/fpspreadsheet/source/common/fpsclasses.pas +++ b/components/fpspreadsheet/source/common/fpsclasses.pas @@ -1225,6 +1225,7 @@ begin P^.NumberFormat := AItem.NumberFormat; P^.NumberFormatStr := AItem.NumberFormatStr; P^.BiDiMode := AItem.BiDiMode; + P^.Protection := AItem.Protection; Result := inherited Add(P); end; end; @@ -1346,6 +1347,8 @@ begin if (uffBiDi in AItem.UsedFormattingFields) then if (P^.BiDiMode <> AItem.BiDiMode) then continue; + if (P^.Protection <> AItem.Protection) then continue; + // If we arrive here then the format records match. exit; end; diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 809cd0401..1682f73b7 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -31,13 +31,17 @@ type TsWorkbook = class; {@@ Worksheet user interface options: - @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 soHasFrozenPanes If set a number of rows and columns of the spreadsheet - is fixed and does not scroll. The number is defined by - LeftPaneWidth and TopPaneHeight. - @param soHidden Worksheet is hidden. } - TsSheetOption = (soShowGridLines, soShowHeaders, soHasFrozenPanes, soHidden); + @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 soHasFrozenPanes If set a number of rows and columns of the + spreadsheet is fixed and does not scroll. The number + is defined by LeftPaneWidth and TopPaneHeight. + @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 @ see TsSheetOption } @@ -99,7 +103,9 @@ type FDefaultRowHeight: Single; // in "character heights", i.e. line count FSortParams: TsSortParams; // Parameters of the current sorting operation FBiDiMode: TsBiDiMode; + FCryptoInfo: TsCryptoInfo; FPageLayout: TsPageLayout; + FProtection: TsWorksheetProtections; FVirtualColCount: Cardinal; FVirtualRowCount: Cardinal; FZoomFactor: Double; @@ -197,6 +203,7 @@ type function ReadVertAlignment(ACell: PCell): TsVertAlignment; function ReadWordwrap(ACell: PCell): boolean; function ReadBiDiMode(ACell: PCell): TsBiDiMode; + function ReadProtection(ACell: PCell): TsCellProtections; function IsEmpty: Boolean; @@ -375,6 +382,11 @@ type function WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; 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 } function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula; procedure CalcFormula(ACell: PCell); @@ -544,7 +556,11 @@ type AOffsetX: Double = 0.0; AOffsetY: Double = 0.0; AScaleX: Double = 1.0; 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 ChangedCol(ACol: Cardinal); procedure ChangedFont(ARow, ACol: Cardinal); @@ -557,6 +573,8 @@ type property Cells: TsCells read FCells; {@@ List of all column records of the worksheet having a non-standard column width } property Cols: TIndexedAVLTree read FCols; + {@@ Information how the worksheet is encrypted } + property CryptoInfo: TsCryptoInfo read FCryptoInfo write FCryptoInfo; {@@ List of all comment records } property Comments: TsComments read FComments; {@@ List of merged cells (contains TsCellRange records) } @@ -570,6 +588,8 @@ type property Name: string read FName write SetName; {@@ Parameters to be used for printing by the Office applications } 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 } property Rows: TIndexedAVLTree read FRows; {@@ Workbook to which the worksheet belongs } @@ -698,6 +718,9 @@ type FLog: TStringList; FSearchEngine: TObject; FUnits: TsSizeUnits; + FProtection: TsWorkbookProtections; + FCryptoInfo: TsCryptoInfo; + {FrevisionsCrypto: TsCryptoInfo;} // Commented out because it needs revision handling { Setter/Getter } function GetErrorMsg: String; @@ -841,6 +864,9 @@ type procedure UpdateCaches; procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal); + { Protection } + function IsProtected: Boolean; + { Error messages } procedure AddErrorMsg(const AMsg: String); 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)} property ActiveWorksheet: TsWorksheet read FActiveWorksheet write SelectWorksheet; + property CryptoInfo: TsCryptoInfo read FCryptoInfo write FCryptoInfo; {@@ Retrieves error messages collected during reading/writing } property ErrorMsg: String read GetErrorMsg; {@@ Filename of the saved workbook } @@ -855,6 +882,8 @@ type {@@ Identifies the file format which was detected when reading the file } property FileFormatID: TsSpreadFormatID read FFormatID; 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; {@@ This event fires whenever a new worksheet is added } @@ -1160,6 +1189,8 @@ begin FActiveCellRow := UNASSIGNED_ROW_COL_INDEX; FActiveCellCol := UNASSIGNED_ROW_COL_INDEX; + FProtection := DEFAULT_SHEET_PROTECTIONS; + FOptions := [soShowGridLines, soShowHeaders]; end; @@ -3481,6 +3512,24 @@ begin 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 @@ -4042,6 +4091,25 @@ begin FWorkbook.FOnChangeWorksheet(FWorkbook, self); 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 exits without any change if not. Creates an event OnChangeWorksheet. @@ -6675,7 +6743,7 @@ end; function TsWorksheet.WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; begin Result := GetCell(ARow, ACol); - WriteBiDiMode(Result, AVAlue); + WriteBiDiMode(Result, AValue); end; procedure TsWorksheet.WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); @@ -6694,6 +6762,26 @@ begin ChangedCell(ACell^.Row, ACell^.Col); 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; begin Result := ReadDefaultColWidth(suChars); @@ -8042,6 +8130,13 @@ begin 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. diff --git a/components/fpspreadsheet/source/common/fpstypes.pas b/components/fpspreadsheet/source/common/fpstypes.pas index 2b23f6b1d..46f8288c3 100644 --- a/components/fpspreadsheet/source/common/fpstypes.pas +++ b/components/fpspreadsheet/source/common/fpstypes.pas @@ -638,6 +638,45 @@ type {@@ Switch a cell from left-to-right to right-to-left orientation } 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 } TsCellFormat = record Name: String; @@ -652,6 +691,7 @@ type Background: TsFillPattern; NumberFormatIndex: Integer; BiDiMode: TsBiDiMode; + Protection: TsCellProtections; // next two are deprecated... NumberFormat: TsNumberFormat; NumberFormatStr: String; diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas index 27e1a15ef..e542f8206 100644 --- a/components/fpspreadsheet/source/common/fpsutils.pas +++ b/components/fpspreadsheet/source/common/fpsutils.pas @@ -170,6 +170,7 @@ procedure FixHyperlinkPathDelims(var ATarget: String); procedure InitCell(out ACell: TCell); overload; procedure InitCell(ARow, ACol: Cardinal; out ACell: TCell); overload; +procedure InitCryptoInfo(out AValue: TsCryptoInfo); procedure InitFormatRecord(out AValue: TsCellFormat); procedure InitImageRecord(out AValue: TsImage; ARow, ACol: Cardinal; AOffsetX, AOffsetY, AScaleX, AScaleY: Double); @@ -2079,6 +2080,18 @@ begin ACell.Col := ACol; 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 -------------------------------------------------------------------------------} @@ -2089,7 +2102,10 @@ begin FillChar(AValue, SizeOf(AValue), 0); AValue.BorderStyles := DEFAULT_BORDERSTYLES; 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; {@@ ---------------------------------------------------------------------------- diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index ce06eaf61..a1b432b1e 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -93,9 +93,11 @@ type procedure ReadSharedStrings(ANode: TDOMNode); procedure ReadSheetFormatPr(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadSheetList(ANode: TDOMNode); + procedure ReadSheetProtection(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet); procedure ReadThemeElements(ANode: TDOMNode); procedure ReadThemeColors(ANode: TDOMNode); + procedure ReadWorkbookProtection(ANode: TDOMNode); procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet); protected FFirstNumFormatIndexInFile: Integer; @@ -118,6 +120,7 @@ type FSharedStringsCount: Integer; FFillList: array of PsCellFormat; FBorderList: array of PsCellFormat; + function GetActiveTab: String; procedure Get_rId(AWorksheet: TsWorksheet; out AComment_rId, AFirstHyperlink_rId, ADrawing_rId, ADrawingHF_rId: Integer); protected @@ -153,6 +156,8 @@ type procedure WriteSheetData(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteSheetFormatPr(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 WriteStyleList(AStream: TStream; ANodeName: String); procedure WriteVmlDrawings(AWorksheet: TsWorksheet); @@ -160,6 +165,7 @@ type procedure WriteVMLDrawings_HeaderFooterImages(AWorksheet: TsWorksheet); procedure WriteVMLDrawingRels(AWorksheet: TsWorksheet); procedure WriteWorkbook(AStream: TStream); + procedure WriteWorkbookProtection(AStream: TStream); procedure WriteWorkbookRels(AStream: TStream); procedure WriteWorksheet(AWorksheet: TsWorksheet); procedure WriteWorksheetRels(AWorksheet: TsWorksheet); @@ -802,6 +808,7 @@ var fillData: TFillListData; borderData: TBorderListData; fnt: TsFont; + cp: TsCellProtections; begin node := ANode.FirstChild; while Assigned(node) do @@ -934,6 +941,29 @@ begin childNode := childNode.NextSibling; 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 Include(fmt.UsedFormattingFields, uffFont); if fmt.Border <> [] then @@ -1971,6 +2001,135 @@ begin 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); var sheetViewNode: TDOMNode; @@ -1994,6 +2153,14 @@ begin if s = '0' then 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'); if s <> '' then AWorksheet.ZoomFactor := StrToFloat(s, FPointSeparatorSettings) * 0.01; @@ -2108,6 +2275,66 @@ begin 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); var rownode: TDOMNode; @@ -2178,6 +2405,7 @@ begin ReadXMLStream(Doc, XMLStream); ReadFileVersion(Doc.DocumentElement.FindNode('fileVersion')); ReadDateMode(Doc.DocumentElement.FindNode('workbookPr')); + ReadWorkbookProtection(Doc.DocumentElement.FindNode('workbookProtection')); ReadSheetList(Doc.DocumentElement.FindNode('sheets')); //ReadDefinedNames(Doc.DocumentElement.FindNode('definedNames')); -- don't read here because sheets do not yet exist ReadActiveSheet(Doc.DocumentElement.FindNode('bookViews'), actSheetIndex); @@ -2248,6 +2476,7 @@ begin ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet); ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet); ReadWorksheet(Doc.DocumentElement.FindNode('sheetData'), FWorksheet); + ReadSheetProtection(Doc.DocumentElement.FindNode('sheetProtection'), FWorksheet); ReadMergedCells(Doc.DocumentElement.FindNode('mergeCells'), FWorksheet); ReadHyperlinks(Doc.DocumentElement.FindNode('hyperlinks')); ReadPrintOptions(Doc.DocumentElement.FindNode('printOptions'), FWorksheet); @@ -2469,6 +2698,12 @@ begin AFirstHyperlink_rId := next_rId; 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 "FormattingStyles" which correspond to the section cellXfs of the styles.xml file. } @@ -3187,6 +3422,113 @@ begin '' + s + ''); 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, + ''); +end; + +procedure TsSpreadOOXMLWriter.WriteSheets(AStream: TStream); +var + counter: Integer; + sheet: TsWorksheet; + sheetName: String; + sheetState: String; +begin + AppendToStream(AStream, + ''); + 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( + '', + [sheetname, counter, counter, sheetstate])); + end; + AppendToStream(AStream, + ''); +end; + procedure TsSpreadOOXMLWriter.WriteSheetViews(AStream: TStream; AWorksheet: TsWorksheet); const @@ -3202,6 +3544,7 @@ var bidi: String; zoomscale: String; attr: String; + windowProtection: String; begin // Show gridlines ? showGridLines := StrUtils.IfThen(soShowGridLines in AWorksheet.Options, '', ' showGridLines="0"'); @@ -3231,8 +3574,14 @@ begin // Selected tab? tabSel := StrUtils.IfThen(AWorksheet = FWorkbook.ActiveWorksheet, ' tabSelected="1"', ''); - // SheetView attributes - attr := showGridLines + showHeaders + tabSel + zoomScale + bidi; + // Window protection + 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 if not (soHasFrozenPanes in AWorksheet.Options) or @@ -3312,8 +3661,7 @@ end; { Writes the style list which the workbook has collected in its FormatList } procedure TsSpreadOOXMLWriter.WriteStyleList(AStream: TStream; ANodeName: String); var -// styleCell: TCell; - s, sAlign: String; + s, sAlign, sProtected: String; fontID: Integer; numFmtParams: TsNumFormatParams; numFmtStr: String; @@ -3331,6 +3679,7 @@ begin fmt := FWorkbook.GetPointerToCellFormat(i); s := ''; sAlign := ''; + sProtected := ''; { Number format } if (uffNumberFormat in fmt^.UsedFormattingFields) then @@ -3358,10 +3707,14 @@ begin { Text rotation } if (uffTextRotation in fmt^.UsedFormattingFields) then case fmt^.TextRotation of - trHorizontal : ; - rt90DegreeClockwiseRotation : sAlign := sAlign + Format('textRotation="%d" ', [180]); - rt90DegreeCounterClockwiseRotation: sAlign := sAlign + Format('textRotation="%d" ', [90]); - rtStacked : sAlign := sAlign + Format('textRotation="%d" ', [255]); + trHorizontal: + ; + rt90DegreeClockwiseRotation: + sAlign := sAlign + Format('textRotation="%d" ', [180]); + rt90DegreeCounterClockwiseRotation: + sAlign := sAlign + Format('textRotation="%d" ', [90]); + rtStacked: + sAlign := sAlign + Format('textRotation="%d" ', [255]); end; { Text alignment } @@ -3389,6 +3742,12 @@ begin if (uffBiDi in fmt^.UsedFormattingFields) and (fmt^.BiDiMode <> bdDefault) then sAlign := sAlign + Format('readingOrder="%d" ', [Ord(fmt^.BiDiMode)]); + if sAlign <> '' then + begin + s := s + 'applyAlignment="1" '; + sAlign := ''; + end; + { Fill } if (uffBackground in fmt^.UsedFormattingFields) then begin @@ -3405,14 +3764,27 @@ begin s := s + Format('borderId="%d" applyBorder="1" ', [borderID]); 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 := ''; + end; + { Write everything to stream } - if sAlign = '' then + if (sAlign = '') and (sProtected = '') then AppendToStream(AStream, '') else AppendToStream(AStream, - '', - '', + '', + sAlign + sProtected, ''); end; @@ -4273,16 +4645,7 @@ begin end; procedure TsSpreadOOXMLWriter.WriteWorkbook(AStream: TStream); -var - actTab: String; - sheetName: String; - counter: Integer; - sheet: TsWorksheet; - sheetstate: String; begin - actTab := IfThen(FWorkbook.ActiveWorksheet = nil, '', - 'activeTab="' + IntToStr(FWorkbook.GetWorksheetIndex(FWorkbook.ActiveWorksheet)) + '"'); - AppendToStream(AStream, XML_HEADER); AppendToStream(AStream, Format( @@ -4291,25 +4654,13 @@ begin ''); AppendToStream(AStream, ''); + WriteWorkbookProtection(AStream); AppendToStream(AStream, '' + - '' + + '' + ''); - - AppendToStream(AStream, - ''); - 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( - '', - [sheetname, counter, counter, sheetstate])); - end; - AppendToStream(AStream, - ''); - + WriteSheets(AStream); WriteDefinedNames(AStream); AppendToStream(AStream, @@ -4318,6 +4669,60 @@ begin ''); 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, ''); +end; + procedure TsSpreadOOXMLWriter.WriteWorkbookRels(AStream: TStream); var counter: Integer; @@ -4377,6 +4782,7 @@ begin WriteSheetFormatPr(FSSheets[FCurSheetNum], AWorksheet); WriteCols(FSSheets[FCurSheetNum], AWorksheet); WriteSheetData(FSSheets[FCurSheetNum], AWorksheet); + WriteSheetProtection(FSSheets[FCurSheetNum], AWorksheet); WriteMergedCells(FSSheets[FCurSheetNum], AWorksheet); WriteHyperlinks(FSSheets[FCurSheetNum], AWorksheet, rId_FirstHyperlink); diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index b146abc1b..9649cf21e 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -37,7 +37,7 @@ - + @@ -143,6 +143,10 @@ + + + + diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpr b/components/fpspreadsheet/tests/spreadtestgui.lpr index ecdce40d0..edb225bfe 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpr +++ b/components/fpspreadsheet/tests/spreadtestgui.lpr @@ -13,7 +13,7 @@ uses optiontests, numformatparsertests, formulatests, rpnFormulaUnit, exceltests, emptycelltests, errortests, virtualmodetests, insertdeletetests, celltypetests, sortingtests, copytests, enumeratortests, commenttests, - hyperlinktests, pagelayouttests; + hyperlinktests, pagelayouttests, protectiontests; begin {$IFDEF HEAPTRC}