diff --git a/components/fpspreadsheet/source/common/fpsutils.pas b/components/fpspreadsheet/source/common/fpsutils.pas
index 069ea0f1f..e63470a87 100644
--- a/components/fpspreadsheet/source/common/fpsutils.pas
+++ b/components/fpspreadsheet/source/common/fpsutils.pas
@@ -93,11 +93,14 @@ function GetCellRangeString(ARange: TsCellRange;
function GetCellString(ARow,ACol: Cardinal;
AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String;
function GetColString(AColIndex: Integer): String;
+function GetRowString(ARowIndex: Integer): String;
// -- "R1C1" syntax
function ParseCellRangeString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal;
out AFlags: TsRelFlags): Boolean;
+function ParseCellString_R1C1(const AStr: String; ABaseRow, ABaseCol: Cardinal;
+ out ASheet: String; out ACellRow, ACellCol: Cardinal; out AFlags: TsRelFlags): Boolean; overload;
function ParseCellString_R1C1(const AStr: String; ABaseRow, ABaseCol: Cardinal;
out ACellRow, ACellCol: Cardinal; out AFlags: TsRelFlags): Boolean; overload;
function ParseCellString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
@@ -701,6 +704,22 @@ begin
if rfRelCol in f then Include(AFlags, rfRelCol2);
end;
+function ParseCellString_R1C1(const AStr: String; ABaseRow, ABaseCol: Cardinal;
+ out ASheet: String; out ACellRow, ACellCol: Cardinal;
+ out AFlags: TsRelFlags): Boolean;
+var
+ p: Integer;
+begin
+ p := pos('!', AStr);
+ if p > 0 then begin
+ ASheet := Copy(AStr, 1, p-1);
+ Result := ParseCellString_R1C1(Copy(AStr, p+1, MaxInt), ABaserow, ABaseCol, ACellRow, ACellCol, AFlags);
+ end else begin
+ ASheet := '';
+ Result := ParseCellString_R1C1(AStr, ABaseRow, ABaseCol, ACellRow, ACellCol, AFlags);
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Parses a cell string in "R1C1" notation into zero-based column and row numbers
'AFlags' indicates relative addresses.
@@ -1078,6 +1097,17 @@ begin
end;
end;
+{@@ ----------------------------------------------------------------------------
+ Calculates an Excel row name ('1', '2' etc) from the zero-based row index
+
+ @param ARowIndex Zero-based row index
+ @return Numerical, one-based row name string.
+-------------------------------------------------------------------------------}
+function GetRowString(ARowIndex: Integer): String;
+begin
+ Result := IntToStr(ARowIndex+1);
+end;
+
const
RELCHAR: Array[boolean] of String = ('$', '');
diff --git a/components/fpspreadsheet/source/common/xlsxml.pas b/components/fpspreadsheet/source/common/xlsxml.pas
index 20bcfbce6..35b54aa3d 100644
--- a/components/fpspreadsheet/source/common/xlsxml.pas
+++ b/components/fpspreadsheet/source/common/xlsxml.pas
@@ -43,8 +43,10 @@ type
procedure ReadCell(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow, ACol: Integer);
procedure ReadCellProtection(ANode: TDOMNode; var AFormat: TsCellFormat);
procedure ReadComment(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ACell: PCell);
+ procedure ReadExcelWorkbook(ANode: TDOMNode);
procedure ReadFont(ANode: TDOMNode; var AFormat: TsCellFormat);
procedure ReadInterior(ANode: TDOMNode; var AFormat: TsCellFormat);
+ procedure ReadNames(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadNumberFormat(ANode: TDOMNode; var AFormat: TsCellFormat);
procedure ReadRow(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow: Integer);
procedure ReadStyle(ANode: TDOMNode);
@@ -193,6 +195,11 @@ begin
end;
end;
+
+{===============================================================================
+ TsSpreadExcelXMLReader
+===============================================================================}
+
{@@ ----------------------------------------------------------------------------
Constructor of the ExcelXML reader
-------------------------------------------------------------------------------}
@@ -539,6 +546,41 @@ begin
TsWorksheet(AWorksheet).WriteComment(ACell, txt);
end;
+{@@ ----------------------------------------------------------------------------
+ Reads the "ExcelWorkbook" node
+-------------------------------------------------------------------------------}
+procedure TsSpreadExcelXMLReader.ReadExcelWorkbook(ANode: TDOMNode);
+var
+ s: String;
+ nodeName: String;
+ n: Integer;
+begin
+ if ANode = nil then
+ exit;
+
+ ANode := ANode.FirstChild;
+ while ANode <> nil do begin
+ nodeName := ANode.NodeName;
+ if nodeName = 'ActiveSheet' then begin
+ s := ANode.TextContent;
+ if (s <> '') and TryStrToInt(s, n) then
+ with TsWorkbook(FWorkbook) do
+ SelectWorksheet(GetWorksheetByIndex(n));
+ end else
+ if nodeName = 'ProtectStructure' then begin
+ s := ANode.TextContent;
+ if s = 'True' then
+ FWorkbook.Protection := FWorkbook.Protection + [bpLockStructure];
+ end else
+ if nodeName = 'ProtectWindows' then begin
+ s := ANode.TextContent;
+ if s = 'True' then
+ FWorkbook.Protection := FWorkbook.Protection + [bpLockWindows];
+ end;
+ ANode := ANode.NextSibling;
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Reads the "Styles/Style/Font" node
-------------------------------------------------------------------------------}
@@ -629,6 +671,91 @@ begin
Include(AFormat.UsedFormattingFields, uffBackground);
end;
+{@@ ----------------------------------------------------------------------------
+ Reads a "Worksheet/Names" node
+-------------------------------------------------------------------------------}
+procedure TsSpreadExcelXMLReader.ReadNames(ANode: TDOMNode;
+ AWorksheet: TsBasicWorksheet);
+
+ procedure DoProcess(AStr: String; var ARowIndex, AColIndex: Cardinal;
+ out IsRow: Boolean);
+ var
+ p: Integer;
+ begin
+ p := pos('!', AStr);
+ if p > 0 then AStr := Copy(AStr, p+1, MaxInt);
+ IsRow := AStr[1] in ['R', 'r'];
+ Delete(AStr, 1, 1);
+ if IsRow then
+ ARowIndex := StrToInt(AStr) - 1
+ else
+ AColIndex := StrToInt(AStr) - 1;
+ end;
+
+ procedure DoRepeatedRowsCols(AStr: String);
+ var
+ p: Integer;
+ isRow: Boolean;
+ r1: Cardinal = UNASSIGNED_ROW_COL_INDEX;
+ c1: Cardinal = UNASSIGNED_ROW_COL_INDEX;
+ r2: Cardinal = UNASSIGNED_ROW_COL_INDEX;
+ c2: Cardinal = UNASSIGNED_ROW_COL_INDEX;
+ begin
+ p := pos(':', AStr);
+ // No colon --> Single range, e.g. "=Sheet1!C1"
+ if p = 0 then
+ begin
+ DoProcess(AStr, r1, c1, isRow);
+ r2 := r1;
+ c2 := c1;
+ end else
+ // Colon --> Range block, e.g. "Sheet1!R1:R2"
+ begin
+ DoProcess(copy(AStr, 1, p-1), r1, c1, isRow);
+ DoProcess(copy(AStr, p+1, MaxInt), r2, c2, isRow);
+ end;
+ if isRow then
+ TsWorksheet(AWorksheet).PageLayout.SetRepeatedRows(r1, r2)
+ else
+ TsWorksheet(AWorksheet).PageLayout.SetRepeatedCols(c1, c2);
+ end;
+
+var
+ s, s1: String;
+ nodeName: String;
+ sheet1, sheet2: String;
+ r1, c1, r2, c2: Cardinal;
+ flags: TsRelFlags;
+ p, q: Integer;
+begin
+ while ANode <> nil do begin
+ nodeName := ANode.NodeName;
+ if nodeName = 'NamedRange' then begin
+ s := GetAttrValue(ANode, 'ss:Name');
+ 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!
+ end else
+ if s = 'Print_Titles' then begin
+ //
+ s := GetAttrValue(ANode, 'ss:RefersTo');
+ if s <> '' then begin
+ p := pos(',', s);
+ if p > 0 then begin
+ DoRepeatedRowsCols(copy(s, 1, p-1));
+ DoRepeatedRowsCols(copy(s, p+1, MaxInt));
+ end else
+ DoRepeatedRowsCols(s);
+ end;
+ end;
+ end;
+ ANode := ANode.NextSibling;
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Reads a "Styles/Style/NumberFormat" node
-------------------------------------------------------------------------------}
@@ -801,6 +928,11 @@ begin
end;
end;
+ // Hidden
+ s := GetAttrValue(ANode, 'ss:Hidden');
+ if s = '1' then
+ sheet.HideCol(c);
+
inc(c);
end
else
@@ -824,6 +956,11 @@ begin
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
sheet.WriteRowHeight(r, x, suPoints);
+ // Hidden
+ s := GetAttrValue(ANode, 'ss:Hidden');
+ if (s = '1') then
+ sheet.HideRow(r);
+
// Row format
s := GetAttrValue(ANode, 'ss:StyleID');
if s <> '' then begin
@@ -853,12 +990,22 @@ var
nodeName: String;
s: String;
begin
+ if ANode = nil then
+ exit;
+
+ s := GetAttrValue(ANode, 'ss:Protected');
+ if s ='1' then
+ AWorksheet.Options := AWorksheet.Options + [soProtected];
+ ;
+ ANode := ANode.FirstChild;
while ANode <> nil do begin
nodeName := ANode.NodeName;
if nodeName = 'Table' then
ReadTable(ANode.FirstChild, AWorksheet)
else if nodeName = 'WorksheetOptions' then
- ReadWorksheetOptions(ANode.FirstChild, AWorksheet);
+ ReadWorksheetOptions(ANode.FirstChild, AWorksheet)
+ else if nodeName = 'Names' then
+ ReadNames(ANode.FirstChild, AWorksheet);
ANode := ANode.NextSibling;
end;
end;
@@ -870,12 +1017,15 @@ procedure TsSpreadExcelXMLReader.ReadWorksheetOptions(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet);
var
sheet: TsWorksheet absolute AWorksheet;
- node: TDOMNode;
+ node, childnode: TDOMNode;
nodeName: String;
s: String;
x: Double;
n: Integer;
hasFitToPage: Boolean = false;
+ c, r: Cardinal;
+ r1, c1, r2, c2: Cardinal;
+ flags: TsRelFlags;
begin
if ANode = nil then
exit;
@@ -984,7 +1134,110 @@ begin
end;
node := node.NextSibling;
end;
+ end else
+ if nodeName = 'Selected' then
+ TsWorkbook(FWorkbook).ActiveWorksheet := sheet
+ else
+ if nodeName = 'Panes' then begin
+ c := sheet.ActiveCellCol;
+ r := sheet.ActiveCellRow;
+ node := ANode.FirstChild;
+ while node <> nil do begin
+ nodeName := node.NodeName;
+ if nodeName = 'Pane' then begin
+ childnode := node.FirstChild;
+ while childnode <> nil do begin
+ nodeName := childNode.NodeName;
+ if nodeName = 'ActiveRow' then begin
+ s := childNode.TextContent;
+ if (s <> '') and TryStrToInt(s, n) then
+ r := n;
+ end else
+ if nodeName = 'ActiveCol' then begin
+ s := childNode.TextContent;
+ if (s <> '') and TryStrToInt(s, n) then
+ c := n;
+ end;
+ childnode := childNode.NextSibling;
+ end;
+ end;
+ node := node.NextSibling;
+ end;
+ sheet.SelectCell(r, c);
+ end else
+ if nodeName = 'FreezePanes' then
+ sheet.Options := sheet.Options + [soHasFrozenPanes]
+ else
+ if (nodeName = 'TopRowBottomPane') then begin
+ s := ANode.TextContent;
+ if (s <> '') and TryStrToInt(s, n) then
+ sheet.TopPaneHeight := n;
+ end else
+ if (nodeName = 'LeftColumnRightPane') then begin
+ s := ANode.TextContent;
+ if (s <> '') and TryStrToInt(s, n) then
+ sheet.LeftPaneWidth := n;
+ end else
+ if nodeName = 'DoNotDisplayGridlines' then
+ sheet.Options := sheet.Options - [soShowGridLines]
+ else
+ if nodeName = 'DoNotDisplayHeadings' then
+ sheet.Options := sheet.Options - [soShowHeaders]
+ else
+ if nodeName = 'Zoom' then begin
+ s := ANode.TextContent;
+ if (s <> '') and TryStrToFloat(s, x) then
+ sheet.Zoomfactor := x * 0.01;
+ end else
+ if nodeName = 'Visible' then begin
+ s := ANode.TextContent;
+ if s = 'SheetHidden' then
+ sheet.Options := sheet.Options + [soHidden];
+ end else
+ if nodeName = 'AllowFormatCells' then
+ sheet.Protection := sheet.Protection - [spFormatCells]
+ else
+ if nodeName = 'AllowSizeCols' then
+ sheet.Protection := sheet.Protection - [spFormatColumns]
+ else
+ if nodeName = 'AllowSizeRows' then
+ sheet.Protection := sheet.Protection - [spFormatRows]
+ else
+ if nodeName = 'AllowInsertCols' then
+ sheet.Protection := sheet.Protection - [spInsertColumns]
+ else
+ if nodeName = 'AllowInsertRows' then
+ sheet.Protection := sheet.Protection - [spInsertRows]
+ else
+ if nodeName = 'AllowInsertHyperlinks' then
+ sheet.Protection := sheet.Protection - [spInsertHyperLinks]
+ else
+ if nodeName = 'AllowDeleteCols' then
+ sheet.Protection := sheet.Protection - [spDeleteColumns]
+ else
+ if nodeName = 'AllowDeleteRows' then
+ sheet.Protection := sheet.Protection - [spDeleteRows]
+ else
+ if nodeName = 'AllowSort' then
+ sheet.Protection := sheet.Protection - [spSort]
+ else
+ if nodeName = 'ProtectObjects' then
+ sheet.Protection := sheet.Protection + [spObjects]
+ else
+ {
+ if nodeName = 'ProtectScenarios' then
+ sheet.Protection := sheet.Protection + [spScenarios];
+ else
+ }
+ if nodeName = 'EnableSelection' then begin
+ s := ANode.TextContent;
+ if s = 'NoSelection' then
+ sheet.Protection := sheet.Protection + [spSelectLockedCells, spSelectUnlockedCells]
+ else
+ if s = 'Unlocked' then
+ sheet.Protection := sheet.Protection + [spSelectLockedCells];
end;
+
ANode := ANode.NextSibling;
end;
@@ -1029,7 +1282,7 @@ begin
s := GetAttrValue(ANode, 'ss:Name');
if s <> '' then begin // the case of '' should not happen
FWorksheet := TsWorkbook(FWorkbook).AddWorksheet(s);
- ReadWorksheet(ANode.FirstChild, FWorksheet);
+ ReadWorksheet(ANode, FWorksheet);
end;
end;
ANode := ANode.NextSibling;
@@ -1047,14 +1300,26 @@ var
begin
try
ReadXMLStream(doc, AStream);
+
+ // Read style list
ReadStyles(doc.DocumentElement.FindNode('Styles'));
+
+ // Read worksheets and their contents
ReadWorksheets(doc.DocumentElement.FindNode('Worksheet'));
+
+ // Read ExcelWorkbook node after worksheet nodes although before it is
+ // found before the worksheet nodes in the file, because is requires
+ // worksheets to be existing.
+ ReadExcelWorkbook(doc.DocumentElement.FindNode('ExcelWorkbook'));
finally
doc.Free;
end;
end;
+{===============================================================================
+ TsSpreadExcelXMLWriter
+===============================================================================}
{@@ ----------------------------------------------------------------------------
Constructor of the ExcelXML writer
@@ -1815,6 +2080,8 @@ begin
StrUtils.IfThen(AWorksheet.IsProtected {and [spScenarios in AWorksheet.Protection])}, 'True', 'False')
]);
+ // todo - Several protection options
+
// Put it all together...
AppendToStream(AStream, INDENT2 +
'' + LF + INDENT3 +
diff --git a/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas b/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas
index 25c8e3f26..84dad2c65 100644
--- a/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas
+++ b/components/fpspreadsheet/source/visual/fpspreadsheetctrls.pas
@@ -4106,6 +4106,41 @@ begin
else
AStrings.Add(' FooterImage, right=');
+ if ASheet.PageLayout.NumPrintRanges = 0 then
+ AStrings.Add(' Print ranges=')
+ else
+ for i := 0 to ASheet.PageLayout.NumPrintRanges-1 do
+ with ASheet.PageLayout.PrintRange[i] do
+ AStrings.Add(Format(' Print range #%d=$%s$%s:$%s$%s', [ i,
+ GetColString(Col1), GetRowString(Row1), GetColString(Col2), GetRowString(Row2)
+ ]));
+
+ if ASheet.PageLayout.RepeatedRows.FirstIndex = UNASSIGNED_ROW_COL_INDEX then
+ AStrings.Add(' Repeated rows=')
+ else
+ if ASheet.PageLayout.RepeatedRows.FirstIndex = ASheet.PageLayout.RepeatedRows.LastIndex then
+ AStrings.Add(Format(' Repeated rows=$%s', [
+ GetRowString(ASheet.PageLayout.RepeatedRows.FirstIndex)
+ ]))
+ else
+ AStrings.Add(Format(' Repeated rows=$%s:$%s', [
+ GetRowString(ASheet.PageLayout.RepeatedRows.FirstIndex),
+ GetRowString(ASheet.PageLayout.RepeatedRows.lastIndex)
+ ]));
+
+ if ASheet.PageLayout.RepeatedCols.FirstIndex = UNASSIGNED_ROW_COL_INDEX then
+ AStrings.Add(' Repeated columns=')
+ else
+ if ASheet.PageLayout.RepeatedCols.FirstIndex = ASheet.PageLayout.RepeatedCols.LastIndex then
+ AStrings.Add(Format(' Repeated columns=$%s', [
+ GetColString(ASheet.PageLayout.RepeatedCols.FirstIndex)
+ ]))
+ else
+ AStrings.Add(Format(' Repeated columns=$%s:$%s', [
+ GetColString(ASheet.PageLayout.RepeatedCols.FirstIndex),
+ GetColString(ASheet.PageLayout.RepeatedCols.lastIndex)
+ ]));
+
s := '';
for po in TsPrintOption do
if po in ASheet.PageLayout.Options then s := s + '; ' + GetEnumName(typeInfo(TsPrintOption), ord(po));