You've already forked lazarus-ccr
fpspreadsheet: Fix written oversized worksheets being defective, adapt BIFF writer's WriteDimensions method to be compatible with oversized worksheets.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3455 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -2939,6 +2939,8 @@ var
|
||||
begin
|
||||
// some abbreviations...
|
||||
defFontSize := Workbook.GetFont(0).Size;
|
||||
GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol);
|
||||
{
|
||||
firstCol := ASheet.GetFirstColIndex;
|
||||
firstRow := ASheet.GetFirstRowIndex;
|
||||
lastCol := ASheet.GetLastColIndex;
|
||||
@ -2946,6 +2948,7 @@ begin
|
||||
// avoid arithmetic overflow in case of empty worksheet
|
||||
if (firstCol = $FFFFFFFF) and (lastCol = 0) then firstCol := 0;
|
||||
if (FirstRow = $FFFFFFFF) and (lastRow = 0) then firstRow := 0;
|
||||
}
|
||||
emptyRowsAbove := firstRow > 0;
|
||||
|
||||
// Now loop through all rows
|
||||
@ -3103,8 +3106,8 @@ begin
|
||||
FPointSeparatorSettings.DecimalSeparator:='.';
|
||||
|
||||
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
|
||||
FLimitations.MaxCols := 1024;
|
||||
FLimitations.MaxRows := 1048576;
|
||||
FLimitations.MaxColCount := 1024;
|
||||
FLimitations.MaxRowCount := 1048576;
|
||||
end;
|
||||
|
||||
destructor TsSpreadOpenDocWriter.Destroy;
|
||||
|
@ -26,8 +26,8 @@ type
|
||||
|
||||
{@@ Record collection limitations of a particular file format }
|
||||
TsSpreadsheetFormatLimitations = record
|
||||
MaxRows: Cardinal;
|
||||
MaxCols: Cardinal;
|
||||
MaxRowCount: Cardinal;
|
||||
MaxColCount: Cardinal;
|
||||
end;
|
||||
|
||||
const
|
||||
@ -6173,8 +6173,8 @@ constructor TsCustomSpreadWriter.Create(AWorkbook: TsWorkbook);
|
||||
begin
|
||||
inherited Create(AWorkbook);
|
||||
{ A good starting point valid for many formats... }
|
||||
FLimitations.MaxCols := 256;
|
||||
FLimitations.MaxRows := 65536;
|
||||
FLimitations.MaxColCount := 256;
|
||||
FLimitations.MaxRowCount := 65536;
|
||||
end;
|
||||
|
||||
{@@
|
||||
@ -6265,7 +6265,8 @@ end;
|
||||
|
||||
{@@
|
||||
Determines the size of the worksheet to be written. VirtualMode is respected.
|
||||
Is called when the writer needs the size for output.
|
||||
Is called when the writer needs the size for output. Column and row count
|
||||
limitations are repsected as well.
|
||||
|
||||
@param AWorksheet Worksheet to be written
|
||||
@param AFirsRow Index of first row to be written
|
||||
@ -6276,15 +6277,26 @@ end;
|
||||
procedure TsCustomSpreadWriter.GetSheetDimensions(AWorksheet: TsWorksheet;
|
||||
out AFirstRow, ALastRow, AFirstCol, ALastCol: Cardinal);
|
||||
begin
|
||||
AFirstRow := 0;
|
||||
AFirstCol := 0;
|
||||
if (boVirtualMode in AWorksheet.Workbook.Options) then begin
|
||||
AFirstRow := 0;
|
||||
AFirstCol := 0;
|
||||
ALastRow := AWorksheet.Workbook.VirtualRowCount-1;
|
||||
ALastCol := AWorksheet.Workbook.VirtualColCount-1;
|
||||
end else begin
|
||||
Workbook.UpdateCaches;
|
||||
AFirstRow := AWorksheet.GetFirstRowIndex;
|
||||
AFirstCol := AWorksheet.GetFirstColIndex;
|
||||
ALastRow := AWorksheet.GetLastRowIndex;
|
||||
ALastCol := AWorksheet.GetLastColIndex;
|
||||
end;
|
||||
if AFirstCol >= Limitations.MaxColCount then
|
||||
AFirstCol := Limitations.MaxColCount-1;
|
||||
if AFirstRow >= Limitations.MaxRowCount then
|
||||
AFirstRow := Limitations.MaxRowCount-1;
|
||||
if ALastCol >= Limitations.MaxColCount then
|
||||
ALastCol := Limitations.MaxColCount-1;
|
||||
if ALastRow >= Limitations.MaxRowCount then
|
||||
ALastRow := Limitations.MaxRowCount-1;
|
||||
end;
|
||||
|
||||
{@@
|
||||
@ -6307,10 +6319,10 @@ var
|
||||
lastCol, lastRow: Cardinal;
|
||||
begin
|
||||
Workbook.GetLastRowColIndex(lastRow, lastCol);
|
||||
if lastRow >= FLimitations.MaxRows then
|
||||
Workbook.AddErrorMsg(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRows]);
|
||||
if lastCol >= FLimitations.MaxCols then
|
||||
Workbook.AddErrorMsg(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxCols]);
|
||||
if lastRow >= FLimitations.MaxRowCount then
|
||||
Workbook.AddErrorMsg(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRowCount]);
|
||||
if lastCol >= FLimitations.MaxColCount then
|
||||
Workbook.AddErrorMsg(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxColCount]);
|
||||
end;
|
||||
|
||||
|
||||
|
@ -30,7 +30,6 @@ type
|
||||
procedure TestWriteErrorMessages_BIFF8;
|
||||
procedure TestWriteErrorMessages_ODS;
|
||||
procedure TestWriteErrorMessages_OOXML;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
@ -62,10 +61,14 @@ var
|
||||
row, col: Cardinal;
|
||||
row1, row2: Cardinal;
|
||||
col1, col2: Cardinal;
|
||||
formula: TsFormula;
|
||||
s: String;
|
||||
TempFile: String;
|
||||
ErrList: TStringList;
|
||||
begin
|
||||
formula.FormulaStr := '=A1';
|
||||
formula.DoubleValue := 0.0;
|
||||
|
||||
ErrList := TStringList.Create;
|
||||
try
|
||||
// Test 1: Too many rows
|
||||
@ -78,7 +81,8 @@ begin
|
||||
MyWorksheet.WriteBlank(row, 0);
|
||||
MyWorksheet.WriteNumber(row, 1, 1.0);
|
||||
MyWorksheet.WriteUTF8Text(row, 2, 'A');
|
||||
MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula(
|
||||
MyWorksheet.WriteFormula(Row, 3, formula);
|
||||
MyWorksheet.WriteRPNFormula(row, 4, CreateRPNFormula(
|
||||
RPNCellValue('A1', nil)));
|
||||
end;
|
||||
TempFile:=NewTempFile;
|
||||
@ -97,10 +101,11 @@ begin
|
||||
col1 := MAX_COL_COUNT[TTestFormat(AFormat)] - 5;
|
||||
col2 := MAX_COL_COUNT[TTestFormat(AFormat)] + 5;
|
||||
for col := col1 to col2 do begin
|
||||
MyWorksheet.WriteBlank(row, 0);
|
||||
MyWorksheet.WriteNumber(row, 1, 1.0);
|
||||
MyWorksheet.WriteUTF8Text(row, 2, 'A');
|
||||
MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula(
|
||||
MyWorksheet.WriteBlank(0, col);
|
||||
MyWorksheet.WriteNumber(1, col, 1.0);
|
||||
MyWorksheet.WriteUTF8Text(2, col, 'A');
|
||||
MyWorksheet.WriteFormula(3, col, formula);
|
||||
MyWorksheet.WriteRPNFormula(4, col, CreateRPNFormula(
|
||||
RPNCellValue('A1', nil)));
|
||||
end;
|
||||
TempFile:=NewTempFile;
|
||||
|
@ -48,7 +48,6 @@
|
||||
<Unit1>
|
||||
<Filename Value="datetests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="datetests"/>
|
||||
</Unit1>
|
||||
<Unit2>
|
||||
<Filename Value="stringtests.pas"/>
|
||||
@ -63,7 +62,6 @@
|
||||
<Unit4>
|
||||
<Filename Value="manualtests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="manualtests"/>
|
||||
</Unit4>
|
||||
<Unit5>
|
||||
<Filename Value="testsutility.pas"/>
|
||||
@ -78,7 +76,6 @@
|
||||
<Unit7>
|
||||
<Filename Value="formattests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="formattests"/>
|
||||
</Unit7>
|
||||
<Unit8>
|
||||
<Filename Value="colortests.pas"/>
|
||||
@ -91,7 +88,6 @@
|
||||
<Unit10>
|
||||
<Filename Value="optiontests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="optiontests"/>
|
||||
</Unit10>
|
||||
<Unit11>
|
||||
<Filename Value="numformatparsertests.pas"/>
|
||||
@ -100,17 +96,14 @@
|
||||
<Unit12>
|
||||
<Filename Value="rpnformulaunit.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="rpnFormulaUnit"/>
|
||||
</Unit12>
|
||||
<Unit13>
|
||||
<Filename Value="formulatests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="formulatests"/>
|
||||
</Unit13>
|
||||
<Unit14>
|
||||
<Filename Value="emptycelltests.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="emptycelltests"/>
|
||||
</Unit14>
|
||||
<Unit15>
|
||||
<Filename Value="errortests.pas"/>
|
||||
|
@ -1502,7 +1502,7 @@ var
|
||||
s: ansistring;
|
||||
xf: Word;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
RPNLength := 0;
|
||||
@ -1608,7 +1608,7 @@ var
|
||||
xf: Word;
|
||||
rec: TBlankRecord;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
xf := FindXFIndex(ACell);
|
||||
@ -1667,7 +1667,7 @@ var
|
||||
var
|
||||
xf: Word;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
if AValue = '' then Exit; // Writing an empty text doesn't work
|
||||
@ -1730,7 +1730,7 @@ var
|
||||
xf: Word;
|
||||
rec: TBIFF2NumberRecord;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
xf := FindXFIndex(ACell);
|
||||
@ -1763,8 +1763,8 @@ var
|
||||
w: Word;
|
||||
h: Single;
|
||||
begin
|
||||
if (ARowIndex >= FLimitations.MaxRows) or (AFirstColIndex >= FLimitations.MaxCols)
|
||||
or (ALastColIndex >= FLimitations.MaxCols)
|
||||
if (ARowIndex >= FLimitations.MaxRowCount) or (AFirstColIndex >= FLimitations.MaxColCount)
|
||||
or (ALastColIndex >= FLimitations.MaxColCount)
|
||||
then
|
||||
exit;
|
||||
|
||||
|
@ -307,6 +307,16 @@ const
|
||||
);
|
||||
|
||||
type
|
||||
TBIFF5DimensionsRecord = packed record
|
||||
RecordID: Word;
|
||||
RecordSize: Word;
|
||||
FirstRow: Word;
|
||||
LastRowPlus1: Word;
|
||||
FirstCol: Word;
|
||||
LastColPlus1: Word;
|
||||
NotUsed: Word;
|
||||
end;
|
||||
|
||||
TBIFF5LabelRecord = packed record
|
||||
RecordID: Word;
|
||||
RecordSize: Word;
|
||||
@ -431,7 +441,7 @@ begin
|
||||
WriteWindow2(AStream, sheet);
|
||||
WritePane(AStream, sheet, true, pane); // true for "is BIFF5 or BIFF8"
|
||||
WriteSelection(AStream, sheet, pane);
|
||||
WriteRows(AStream, sheet);
|
||||
//WriteRows(AStream, sheet);
|
||||
|
||||
if (boVirtualMode in Workbook.Options) then
|
||||
WriteVirtualCells(AStream)
|
||||
@ -551,8 +561,27 @@ end;
|
||||
}
|
||||
procedure TsSpreadBIFF5Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
|
||||
var
|
||||
lLastCol, lLastRow: Word;
|
||||
rec: TBIFF5DimensionsRecord;
|
||||
firstCol, lastCol, firstRow, lastRow: Cardinal;
|
||||
lLastRow, lLastCol: Word;
|
||||
begin
|
||||
{ Determine sheet size }
|
||||
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
|
||||
|
||||
{ Setup BIFF record }
|
||||
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
|
||||
rec.RecordSize := WordToLE(10);
|
||||
rec.FirstRow := WordToLE(firstRow);
|
||||
rec.LastRowPlus1 := WordToLE(lastRow+1);
|
||||
rec.FirstCol := WordToLe(firstCol);
|
||||
rec.LastColPlus1 := WordToLE(lastCol+1);
|
||||
rec.NotUsed := 0;
|
||||
|
||||
{ Write BIFF record }
|
||||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||||
|
||||
(*
|
||||
|
||||
{ BIFF Record header }
|
||||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DIMENSIONS));
|
||||
AStream.WriteWord(WordToLE(10));
|
||||
@ -573,6 +602,7 @@ begin
|
||||
|
||||
{ Not used }
|
||||
AStream.WriteWord(0);
|
||||
*)
|
||||
end;
|
||||
|
||||
{*******************************************************************
|
||||
@ -951,7 +981,7 @@ var
|
||||
rec: TBIFF5LabelRecord;
|
||||
buf: array of byte;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
case WorkBookEncoding of
|
||||
|
@ -34,9 +34,11 @@ EOF
|
||||
The row and column numbering in BIFF files is zero-based.
|
||||
|
||||
Excel file format specification obtained from:
|
||||
|
||||
http://sc.openoffice.org/excelfileformat.pdf
|
||||
|
||||
see also:
|
||||
http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
|
||||
|
||||
AUTHORS: Felipe Monteiro de Carvalho
|
||||
Jose Mejuto
|
||||
}
|
||||
@ -280,6 +282,16 @@ const
|
||||
);
|
||||
|
||||
type
|
||||
TBIFF8DimensionsRecord = packed record
|
||||
RecordID: Word;
|
||||
RecordSize: Word;
|
||||
FirstRow: DWord;
|
||||
LastRowPlus1: DWord;
|
||||
FirstCol: Word;
|
||||
LastColPlus1: Word;
|
||||
NotUsed: Word;
|
||||
end;
|
||||
|
||||
TBIFF8LabelRecord = packed record
|
||||
RecordID: Word;
|
||||
RecordSize: Word;
|
||||
@ -578,7 +590,24 @@ end;
|
||||
procedure TsSpreadBIFF8Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
|
||||
var
|
||||
firstRow, lastRow, firstCol, lastCol: Cardinal;
|
||||
rec: TBIFF8DimensionsRecord;
|
||||
begin
|
||||
{ Determine sheet size }
|
||||
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
|
||||
|
||||
{ Populate BIFF record }
|
||||
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
|
||||
rec.RecordSize := WordToLE(14);
|
||||
rec.FirstRow := DWordToLE(firstRow);
|
||||
rec.LastRowPlus1 := DWordToLE(lastRow+1);
|
||||
rec.FirstCol := WordToLE(firstCol);
|
||||
rec.LastColPlus1 := WordToLE(lastCol+1);
|
||||
rec.NotUsed := 0;
|
||||
|
||||
{ Write BIFF record to stream }
|
||||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||||
|
||||
(*
|
||||
{ BIFF Record header }
|
||||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DIMENSIONS));
|
||||
AStream.WriteWord(WordToLE(14));
|
||||
@ -600,6 +629,7 @@ begin
|
||||
|
||||
{ Not used }
|
||||
AStream.WriteWord(WordToLE(0));
|
||||
*)
|
||||
end;
|
||||
|
||||
{*******************************************************************
|
||||
@ -996,7 +1026,7 @@ var
|
||||
rec: TBIFF8LabelRecord;
|
||||
buf: array of byte;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
WideValue := UTF8Decode(AValue); //to UTF16
|
||||
|
@ -1852,7 +1852,7 @@ procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream;
|
||||
var
|
||||
rec: TBIFF58BlankRecord;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
{ BIFF record header }
|
||||
@ -1925,7 +1925,7 @@ var
|
||||
w: Integer;
|
||||
begin
|
||||
if Assigned(ACol) then begin
|
||||
if (ACol^.Col >= FLimitations.MaxCols) then
|
||||
if (ACol^.Col >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
{ BIFF record header }
|
||||
@ -2041,7 +2041,7 @@ procedure TsSpreadBIFFWriter.WriteNumber(AStream: TStream;
|
||||
var
|
||||
rec: TBIFF58NumberRecord;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
{ BIFF Record header }
|
||||
@ -2269,7 +2269,7 @@ var
|
||||
RPNLength: Word;
|
||||
RecordSizePos, FinalPos: Int64;
|
||||
begin
|
||||
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
|
||||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||||
exit;
|
||||
|
||||
{ BIFF Record header }
|
||||
@ -2494,9 +2494,9 @@ var
|
||||
rowheight: Word;
|
||||
h: Single;
|
||||
begin
|
||||
if (ARowIndex >= FLimitations.MaxRows) or
|
||||
(AFirstColIndex >= FLimitations.MaxCols) or
|
||||
(ALastColIndex >= FLimitations.MaxCols)
|
||||
if (ARowIndex >= FLimitations.MaxRowCount) or
|
||||
(AFirstColIndex >= FLimitations.MaxColCount) or
|
||||
(ALastColIndex >= FLimitations.MaxColCount)
|
||||
then
|
||||
exit;
|
||||
|
||||
|
@ -1857,9 +1857,11 @@ begin
|
||||
AppendToStream(AStream,
|
||||
'<sheetData>');
|
||||
|
||||
GetSheetDimensions(AWorksheet, r1, r2, c1, c2);
|
||||
|
||||
if (boVirtualMode in Workbook.Options) and Assigned(Workbook.OnWriteCellData)
|
||||
then begin
|
||||
for r := 0 to Workbook.VirtualRowCount-1 do begin
|
||||
for r := 0 to r2 do begin
|
||||
row := AWorksheet.FindRow(r);
|
||||
if row <> nil then
|
||||
rh := Format(' ht="%g" customHeight="1"', [
|
||||
@ -1868,7 +1870,7 @@ begin
|
||||
rh := '';
|
||||
AppendToStream(AStream, Format(
|
||||
'<row r="%d" spans="1:%d"%s>', [r+1, Workbook.VirtualColCount, rh]));
|
||||
for c := 0 to Workbook.VirtualColCount-1 do begin
|
||||
for c := 0 to c2 do begin
|
||||
InitCell(lCell);
|
||||
value := varNull;
|
||||
styleCell := nil;
|
||||
@ -1905,6 +1907,7 @@ begin
|
||||
end else
|
||||
begin
|
||||
// The cells need to be written in order, row by row, cell by cell
|
||||
(*
|
||||
c1 := AWorksheet.GetFirstColIndex;
|
||||
c2 := AWorksheet.GetLastColIndex;
|
||||
if (c1 = $FFFFFFFF) and (c2 = 0) then c1 := 0; // avoid arithmetic overflow in case of empty worksheet
|
||||
@ -1912,6 +1915,7 @@ begin
|
||||
r2 := AWorksheet.GetlastRowIndex;
|
||||
if (r1 = $FFFFFFFF) and (r2 = 0) then r1 := 0; // avoid arithmetic overflow in case of empty worksheet
|
||||
// for r := 0 to AWorksheet.GetLastRowIndex do begin
|
||||
*)
|
||||
for r := r1 to r2 do begin
|
||||
// If the row has a custom height add this value to the <row> specification
|
||||
row := AWorksheet.FindRow(r);
|
||||
@ -2298,8 +2302,8 @@ begin
|
||||
FPointSeparatorSettings.DecimalSeparator := '.';
|
||||
|
||||
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
|
||||
FLimitations.MaxCols := 16384;
|
||||
FLimitations.MaxRows := 1048576;
|
||||
FLimitations.MaxColCount := 16384;
|
||||
FLimitations.MaxRowCount := 1048576;
|
||||
end;
|
||||
|
||||
procedure TsSpreadOOXMLWriter.CreateNumFormatList;
|
||||
|
Reference in New Issue
Block a user