diff --git a/components/fpspreadsheet/source/common/xlsxml.pas b/components/fpspreadsheet/source/common/xlsxml.pas index 2d21e5414..13ca158de 100644 --- a/components/fpspreadsheet/source/common/xlsxml.pas +++ b/components/fpspreadsheet/source/common/xlsxml.pas @@ -50,6 +50,7 @@ type procedure ReadNames(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); procedure ReadNumberFormat(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadPageSetup(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); + procedure ReadPrint(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); procedure ReadRow(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow: Integer); procedure ReadStyle(ANode: TDOMNode); procedure ReadStyles(ANode: TDOMNode); @@ -82,9 +83,11 @@ type function GetPageFooterStr(AWorksheet: TsBasicWorksheet): String; function GetPageHeaderStr(AWorksheet: TsBasicWorksheet): String; function GetPageMarginStr(AWorksheet: TsBasicWorksheet): String; + function GetPrintStr(AWorksheet: TsBasicWorksheet): String; function GetStyleStr(AFormatIndex: Integer): String; procedure WriteColumns(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteExcelWorkbook(AStream: TStream); + procedure WriteNames(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteRows(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteStyle(AStream: TStream; AIndex: Integer); procedure WriteStyles(AStream: TStream); @@ -142,6 +145,8 @@ const INDENT3 = ' '; INDENT4 = ' '; INDENT5 = ' '; + NAMES_INDENT = INDENT2; + NAME_INDENT = INDENT3; TABLE_INDENT = INDENT2; ROW_INDENT = INDENT3; COL_INDENT = INDENT3; @@ -787,13 +792,16 @@ procedure TsSpreadExcelXMLReader.ReadNames(ANode: TDOMNode; end; var - s: String; + sheet: TsWorksheet absolute AWorksheet; + s, sr: String; nodeName: String; sheet1, sheet2: String; r1, c1, r2, c2: Cardinal; flags: TsRelFlags; p: Integer; + ok: Boolean; begin + ok := true; while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'NamedRange' then begin @@ -801,9 +809,27 @@ begin if s = 'Print_Area' then begin // s := GetAttrValue(ANode, 'ss:RefersTo'); - if (s <> '') and ParseCellRangeString_R1C1(s, 0, 0, sheet1, sheet2, r1, c1, r2, c2, flags) then - TsWorksheet(AWorksheet).PageLayout.AddPrintRange(r1, c1, r2, c2); - // to do: include sheet names here! + if (s <> '') then begin + p := pos(',', s); + while p > 0 do begin + sr := Copy(s, 1, p-1); + if ParseCellRangeString_R1C1(sr, 0, 0, sheet1, sheet2, r1, c1, r2, c2, flags) then + sheet.PageLayout.AddPrintRange(r1, c1, r2, c2) + else begin + FWorkbook.AddErrorMsg('Invalid print range.'); + ok := false; + break; + end; + s := copy(s, p+1, MaxInt); + p := pos(',', s); + end; + if ok then begin + if ParseCellRangeString_R1C1(s, 0, 0, sheet1, sheet2, r1, c1, r2, c2, flags) then + sheet.PageLayout.AddPrintRange(r1, c1, r2, c2) + else + FWorkbook.AddErrorMsg('Invalid print range.'); + end; + end; end else if s = 'Print_Titles' then begin // @@ -944,6 +970,63 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Reads the "WorksheetOptions/Print" node +-------------------------------------------------------------------------------} +procedure TsSpreadExcelXMLReader.ReadPrint(ANode: TDOMNode; + AWorksheet: TsBasicWorksheet); +var + sheet: TsWorksheet absolute AWorksheet; + nodeName: String; + s: String; + n: Integer; + x: Double; +begin + while ANode <> nil do begin + nodeName := ANode.NodeName; + if nodeName = 'PaperSizeIndex' then begin + s := ANode.TextContent; + if (s <> '') and TryStrToInt(s, n) and (n < Length(PAPER_SIZES)) then begin + sheet.PageLayout.PageWidth := PAPER_SIZES[n, 0]; + sheet.PageLayout.pageHeight := PAPER_SIZES[n, 1]; + end; + end + else if nodeName = 'FitHeight' then begin + s := ANode.TextContent; + if (s <> '') and TryStrToInt(s, n) then + sheet.PageLayout.FitHeightToPages := n; + end + else if nodeName = 'FitWidth' then begin + s := ANode.TextContent; + if (s <> '') and TryStrToInt(s, n) then + sheet.PageLayout.FitWidthToPages := n; + end + else if nodeName = 'Scale' then begin + s := ANode.TextContent; + if (s <> '') and TryStrToInt(s, n) then + sheet.PageLayout.ScalingFactor := n; + end + else if nodeName = 'Gridlines' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintGridLines] + else if nodeName = 'BlackAndWhite' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poMonochrome] + else if nodeName = 'DraftQuality' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poDraftQuality] + else if nodeName = 'LeftToRight' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintPagesByRows] + else if nodeName = 'RowColHeadings' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintHeaders] + else if nodeName = 'CommentsLayout' then begin + s := ANode.TextContent; + if s = 'SheetEnd' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poCommentsAtEnd] + else if s = 'InPlace' then + sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintCellComments]; + end; + ANode := ANode.NextSibling; + end; +end; + {@@ ---------------------------------------------------------------------------- Reads a "Worksheet/Table/Row" node -------------------------------------------------------------------------------} @@ -1195,49 +1278,7 @@ begin end else if nodeName = 'Print' then begin node := ANode.FirstChild; - while node <> nil do begin - nodeName := node.NodeName; - if nodeName = 'PaperSizeIndex' then begin - s := node.TextContent; - if (s <> '') and TryStrToInt(s, n) and (n < Length(PAPER_SIZES)) then begin - sheet.PageLayout.PageWidth := PAPER_SIZES[n, 0]; - sheet.PageLayout.pageHeight := PAPER_SIZES[n, 1]; - end; - end - else if nodeName = 'FitHeight' then begin - s := node.TextContent; - if (s <> '') and TryStrToInt(s, n) then - sheet.PageLayout.FitHeightToPages := n; - end - else if nodeName = 'FitWidth' then begin - s := node.TextContent; - if (s <> '') and TryStrToInt(s, n) then - sheet.PageLayout.FitWidthToPages := n; - end - else if nodeName = 'Scale' then begin - s := node.TextContent; - if (s <> '') and TryStrToInt(s, n) then - sheet.PageLayout.ScalingFactor := n; - end - else if nodeName = 'Gridlines' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintGridLines] - else if nodeName = 'BlackAndWhite' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poMonochrome] - else if nodeName = 'DraftQuality' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poDraftQuality] - else if nodeName = 'LeftToRight' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintPagesByRows] - else if nodeName = 'RowColHeadings' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintHeaders] - else if nodeName = 'CommentsLayout' then begin - s := node.TextContent; - if s = 'SheetEnd' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poCommentsAtEnd] - else if s = 'InPlace' then - sheet.PageLayout.Options := sheet.PageLayout.Options + [poPrintCellComments]; - end; - node := node.NextSibling; - end; + ReadPrint(ANode.FirstChild, AWorksheet); end else if nodeName = 'Selected' then TsWorkbook(FWorkbook).ActiveWorksheet := sheet @@ -1615,6 +1656,49 @@ begin Result := ''; end; +{ Todo: When can the "Print" node be skipped? } +function TsSpreadExcelXMLWriter.GetPrintStr(AWorksheet: TsBasicWorksheet): String; +var + sheet: TsWorksheet absolute AWorksheet; + i, pgSizeIdx: Integer; + scalestr: String; +begin + Result := ''; + pgSizeIdx := -1; + for i:=0 to High(PAPER_SIZES) do + if (SameValue(PAPER_SIZES[i,0], sheet.PageLayout.PageHeight) and + SameValue(PAPER_SIZES[i,1], sheet.PageLayout.PageWidth)) + or (SameValue(PAPER_SIZES[i,1], sheet.PageLayout.PageHeight) and + SameValue(PAPER_SIZES[i,0], sheet.PageLayout.PageWidth)) + then begin + pgSizeIdx := i; + break; + end; + + if pgSizeidx = -1 then + exit; + + // Scaling factor + if sheet.PageLayout.ScalingFactor <> 100 then + scaleStr := INDENT4 + '' + IntToStr(sheet.PageLayout.ScalingFactor) + '' + LF + else + scaleStr := ''; + + Result := + INDENT4 + '' + LF + + INDENT4 + '' + IntToStr(pgSizeIdx) + '' + LF + + scaleStr + + INDENT4 + '0'; + + if sheet.PageLayout.FitHeightToPages > 1 then + Result := Result + LF + INDENT4 + + '' + IntToStr(sheet.PageLayout.FitHeightToPages) + ''; + + if sheet.PageLayout.FitWidthToPages > 1 then + Result := result + LF + INDENT4 + + '' + IntToStr(sheet.PageLayout.FitWidthToPages) + ''; +end; + function TsSpreadExcelXMLWriter.GetStyleStr(AFormatIndex: Integer): String; begin Result := ''; @@ -1890,6 +1974,70 @@ begin ])); end; +procedure TsSpreadExcelXMLWriter.WriteNames(AStream: TStream; + AWorksheet: TsBasicWorksheet); +var + sheet: TsWorksheet absolute AWorksheet; + print_titles_str: string = ''; + print_range_str: String = ''; + s: String; + rng: TsCellRange; + i: Integer; +begin + with sheet.PageLayout do begin + + // Print ranges --> Name "Print_Area" + for i:=0 to NumPrintRanges-1 do begin + rng := GetPrintRange(i); + s := GetCellRangeString_R1C1(sheet.Name, sheet.Name, rng.Row1, rng.Col1, rng.Row2, rng.Col2, []); + if print_range_str = '' then + print_range_str := s + else + print_range_str := print_range_str + ',' + s; + end; + if print_range_str <> '' then + print_range_str := NAME_INDENT + + '' + LF; + + // Repeated columns --> Name "Print_Titles" + if (RepeatedCols.FirstIndex <> UNASSIGNED_ROW_COL_INDEX) and + (RepeatedCols.LastIndex <> UNASSIGNED_ROW_COL_INDEX) + then begin + s := 'C' + IntToStr(RepeatedCols.FirstIndex + 1); + if RepeatedCols.FirstIndex <> RepeatedCols.LastIndex then + s := s + ':C' + IntToStr(RepeatedCols.LastIndex + 1); + s := sheet.Name + '!' + s; + print_titles_str := s; + end; + + // Repeated rows --> Name "Print_Titles" + if (RepeatedRows.FirstIndex <> UNASSIGNED_ROW_COL_INDEX) and + (RepeatedRows.LastIndex <> UNASSIGNED_ROW_COL_INDEX) + then begin + s := 'R' + IntToStr(RepeatedRows.FirstIndex + 1); + if RepeatedRows.FirstIndex <> RepeatedRows.LastIndex then + s := s + ':R' + IntToStr(RepeatedRows.LastIndex + 1); + s := sheet.Name + '!' + s; + if print_titles_str = '' then + print_titles_str := s + else + print_titles_str := print_titles_str + ',' + s; + end; + if print_titles_str <> '' then + print_titles_str := NAME_INDENT + + '' + LF; + end; + + if (print_range_str = '') and (print_titles_str = '') then + exit; + + AppendToStream(AStream, NAMES_INDENT + + '' + LF + + print_titles_str + NAMES_INDENT + + print_range_str + NAMES_INDENT + + '' + LF); +end; + procedure TsSpreadExcelXMLWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); begin @@ -2229,7 +2377,8 @@ begin FWorksheet := AWorksheet; if FWorksheet.IsProtected then - protectedStr := ' ss:Protected="1"' else + protectedStr := ' ss:Protected="1"' + else protectedStr := ''; AppendToStream(AStream, Format( @@ -2237,6 +2386,7 @@ begin UTF8TextToXMLText(AWorksheet.Name), protectedStr ]) ); + WriteNames(AStream, AWorksheet); WriteTable(AStream, AWorksheet); WriteWorksheetOptions(AStream, AWorksheet); AppendToStream(AStream, @@ -2247,7 +2397,6 @@ end; procedure TsSpreadExcelXMLWriter.WriteWorksheetOptions(AStream: TStream; AWorksheet: TsBasicWorksheet); var - i: Integer; footerStr, headerStr: String; hideGridStr: String; hideHeadersStr: String; @@ -2258,7 +2407,8 @@ var protectStr: String; visibleStr: String; printStr: String; - scaleStr: String; + fitToPageStr: String; + enableSelectionStr: String; sheet: TsWorksheet absolute AWorksheet; begin // Orientation, some PageLayout.Options @@ -2294,26 +2444,14 @@ begin else selectedStr := ''; - // Scaling factor - if sheet.PageLayout.ScalingFactor <> 100 then - scaleStr := '' + IntToStr(sheet.PageLayout.ScalingFactor) + '' + LF + INDENT4 + // FitToPage node + if poFitPages in sheet.PageLayout.Options then + fitToPageStr := INDENT3 + '' + LF else - scaleStr := ''; + fitToPageStr := ''; + // Print node - printStr := ''; - for i:=0 to High(PAPER_SIZES) do - if (SameValue(PAPER_SIZES[i,0], sheet.PageLayout.PageHeight) and - SameValue(PAPER_SIZES[i,1], sheet.PageLayout.PageWidth)) - or (SameValue(PAPER_SIZES[i,1], sheet.PageLayout.PageHeight) and - SameValue(PAPER_SIZES[i,0], sheet.PageLayout.PageWidth)) - then begin - printStr := INDENT4 + - '' + LF + INDENT4 + - '' + IntToStr(i) + '' + LF + INDENT4 + - scaleStr + - '0'; - break; - end; + printStr := GetPrintStr(AWorksheet); // Visible if (soHidden in AWorksheet.Options) then @@ -2327,10 +2465,21 @@ begin // Protection protectStr := Format(INDENT3 + '%s' + LF + INDENT3 + '%s' + LF, [ - StrUtils.IfThen(AWorksheet.IsProtected and (spObjects in AWorksheet.Protection), 'True', 'False'), + StrUtils.IfThen(spObjects in AWorksheet.Protection, 'True', 'False'), StrUtils.IfThen(AWorksheet.IsProtected {and [spScenarios in AWorksheet.Protection])}, 'True', 'False') ]); + // Enable selection + enableSelectionStr := ''; + if (sheet.Protection * [spSelectLockedCells, spSelectUnlockedCells] <> []) then begin + enableSelectionStr := INDENT3 + '' + LF; + if spSelectUnlockedCells in sheet.Protection then + enableSelectionStr := enableSelectionStr + INDENT4 + '' + LF; + if (sheet.Protection * [spSelectLockedCells, spSelectUnlockedCells] = [spSelectLockedCells]) then + enableSelectionStr := enableSelectionStr + INDENT4 + '' + LF; + enableSelectionStr := INDENT3 + '' + LF; + end; + // todo - Several protection options // Put it all together... @@ -2341,12 +2490,23 @@ begin headerStr + footerStr + marginStr + INDENT3 + - '' + LF + INDENT3 + + '' + LF + + fitToPageStr + INDENT3 + '' + LF + printStr + LF + INDENT3 + '' + LF + visibleStr + selectedStr + + IfThen(not (spFormatCells in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spFormatColumns in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spFormatRows in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spDeleteColumns in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spDeleteRows in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spInsertColumns in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spInsertHyperlinks in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spInsertRows in sheet.Protection), INDENT4 + '' + LF) + + IfThen(not (spSort in sheet.Protection), INDENT4 + '' + LF) + + enableSelectionStr + protectStr + frozenStr + hideGridStr + diff --git a/components/fpspreadsheet/tests/protectiontests.pas b/components/fpspreadsheet/tests/protectiontests.pas index 45ab175ad..e7ec70d9f 100644 --- a/components/fpspreadsheet/tests/protectiontests.pas +++ b/components/fpspreadsheet/tests/protectiontests.pas @@ -116,10 +116,8 @@ type procedure TestWriteRead_XML_WorksheetProtection_SelectLockedCells; procedure TestWriteRead_XML_WorksheetProtection_SelectUnlockedCells; procedure TestWriteRead_XML_WorksheetProtection_Objects; - procedure TestWriteRead_XML_CellProtection; - - procedure TestWriteRead_XML_Passwords; + //procedure TestWriteRead_XML_Passwords; // not allowed { ODS protection tests } procedure TestWriteRead_ODS_WorkbookProtection_None; @@ -237,7 +235,7 @@ begin 9: Exclude(expected, spSort); 10: Exclude(expected, spSelectLockedCells); 11: Exclude(expected, spSelectUnlockedCells); - 12: Exclude(expected, spObjects); + 12: Include(expected, spObjects); end; { case ACondition of @@ -785,11 +783,6 @@ begin TestWriteRead_CellProtection(sfExcelXML); end; -procedure TSpreadWriteReadProtectionTests.TestWriteRead_XML_Passwords; -begin - TestWriteRead_Passwords(sfExcelXML); -end; - {------------------------------------------------------------------------------} { Tests for OpenDocument file format }