2016-10-20 21:21:08 +00:00
|
|
|
unit fpsSYLK;
|
|
|
|
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
Classes, SysUtils,
|
2018-06-12 20:06:46 +00:00
|
|
|
fpstypes, fpsReaderWriter, xlsCommon;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
TsSYLKField = record
|
|
|
|
Name: Char;
|
|
|
|
Value: String;
|
|
|
|
end;
|
|
|
|
TsSYLKFields = array of TsSYLKField;
|
|
|
|
|
|
|
|
|
|
|
|
{ TsSYLKReader }
|
|
|
|
|
|
|
|
TsSYLKReader = class(TsCustomSpreadReader)
|
|
|
|
private
|
|
|
|
FWorksheetName: String;
|
|
|
|
FPointSeparatorSettings: TFormatSettings;
|
|
|
|
FDateMode: TDateMode;
|
2019-07-31 22:43:49 +00:00
|
|
|
FPrevX: Integer;
|
|
|
|
FPrevY: Integer;
|
|
|
|
FNumRows, FNumCols: Integer;
|
|
|
|
FRecordLine: String;
|
|
|
|
FLineNumber: Integer;
|
2016-10-20 21:21:08 +00:00
|
|
|
protected
|
|
|
|
function GetFieldValue(const AFields: TsSYLKFields; AFieldName: Char): String;
|
2019-07-31 22:43:49 +00:00
|
|
|
procedure ProcessBounds(const AFields: TsSYLKFields);
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure ProcessCell(const AFields: TsSYLKFields);
|
|
|
|
procedure ProcessFormat(const AFields: TsSYLKFields);
|
|
|
|
procedure ProcessLine(const ALine: String);
|
2019-08-01 09:55:03 +00:00
|
|
|
procedure ProcessNumFormat(const AFields: TsSYLKFields);
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure ProcessRecord(ARecordType: String; const AFields: TsSYLKFields);
|
|
|
|
public
|
2018-06-12 20:06:46 +00:00
|
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
2017-03-19 23:10:55 +00:00
|
|
|
procedure ReadFromFile(AFileName: String; APassword: String = '';
|
|
|
|
AParams: TsStreamParams = []); override;
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ TsSYLKWriter }
|
|
|
|
TsSYLKWriter = class(TsCustomSpreadWriter)
|
|
|
|
private
|
|
|
|
FPointSeparatorSettings: TFormatSettings;
|
|
|
|
FDateMode: TDateMode;
|
|
|
|
FSheetIndex: Integer;
|
|
|
|
function GetFormatStr(ACell: PCell): String;
|
|
|
|
function GetFormulaStr(ACell: PCell): String;
|
|
|
|
protected
|
|
|
|
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: Boolean; ACell: PCell); override;
|
|
|
|
procedure WriteCellToStream(AStream: TStream; ACell: PCell); override;
|
|
|
|
procedure WriteComment(AStream: TStream; ACell: PCell); override;
|
|
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: TDateTime; ACell: PCell); override;
|
|
|
|
procedure WriteDimensions(AStream: TStream);
|
|
|
|
procedure WriteEndOfFile(AStream: TStream);
|
|
|
|
procedure WriteHeader(AStream: TStream);
|
|
|
|
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: string; ACell: PCell); override;
|
|
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: double; ACell: PCell); override;
|
|
|
|
procedure WriteNumberFormatList(AStream: TStream);
|
|
|
|
procedure WriteOptions(AStream: TStream);
|
|
|
|
public
|
2018-06-12 20:06:46 +00:00
|
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
TSYLKSettings = record
|
|
|
|
SheetIndex: Integer; // W
|
|
|
|
DateMode: TDateMode; // R/W
|
|
|
|
end;
|
|
|
|
|
|
|
|
const
|
|
|
|
STR_FILEFORMAT_SYLK = 'SYLK';
|
|
|
|
|
|
|
|
var
|
|
|
|
{@@ Default settings for reading/writing of SYLK files }
|
|
|
|
SYLKSettings: TSYLKSettings = (
|
|
|
|
SheetIndex: 0;
|
|
|
|
DateMode: dm1900
|
|
|
|
);
|
|
|
|
|
|
|
|
{@@ File format identifier }
|
|
|
|
sfidSYLK: Integer;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
2018-06-12 20:06:46 +00:00
|
|
|
fpsUtils, fpsNumFormat, fpspreadsheet;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
{==============================================================================}
|
|
|
|
{ TsSYLKReader }
|
|
|
|
{==============================================================================}
|
|
|
|
|
2018-06-12 20:06:46 +00:00
|
|
|
constructor TsSYLKReader.Create(AWorkbook: TsBasicWorkbook);
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
|
|
|
FWorksheetName := 'Sheet1'; // will be replaced by filename
|
|
|
|
FDateMode := SYLKSettings.DateMode;
|
|
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsSYLKReader.GetFieldValue(const AFields: TsSYLKFields;
|
|
|
|
AFieldName: Char): String;
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
|
|
for i := 0 to Length(AFields)-1 do
|
|
|
|
if AFields[i].Name = AFieldName then begin
|
|
|
|
Result := AFields[i].Value;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
Result := '';
|
|
|
|
end;
|
|
|
|
|
2019-07-31 22:43:49 +00:00
|
|
|
procedure TsSYLKReader.ProcessBounds(const AFields: TsSYLKFields);
|
|
|
|
var
|
|
|
|
srows, scols: String;
|
|
|
|
begin
|
|
|
|
scols := GetFieldValue(AFields, 'X');
|
|
|
|
srows := GetFieldValue(AFields, 'Y');
|
|
|
|
if (scols = '') or (srows = '') then
|
|
|
|
exit;
|
|
|
|
|
|
|
|
FNumRows := StrToInt(srows);
|
|
|
|
FNumCols := StrToInt(scols);
|
|
|
|
end;
|
|
|
|
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure TsSYLKReader.ProcessCell(const AFields: TsSYLKFields);
|
|
|
|
var
|
2019-07-31 22:43:49 +00:00
|
|
|
row, col: Integer;
|
2016-10-20 21:21:08 +00:00
|
|
|
sval, expr: String;
|
|
|
|
val: Double;
|
|
|
|
cell: PCell;
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet: TsWorksheet;
|
2019-07-31 21:29:07 +00:00
|
|
|
book: TsWorkbook;
|
2019-07-31 22:43:49 +00:00
|
|
|
s: String;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
2019-07-31 21:29:07 +00:00
|
|
|
book := FWorkbook as TsWorkbook;
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet := FWorksheet as TsWorksheet;
|
|
|
|
|
2019-07-31 22:43:49 +00:00
|
|
|
s := GetFieldValue(AFields, 'X');
|
|
|
|
if (s <> '') and TryStrToInt(s, col) then begin
|
|
|
|
dec(col);
|
|
|
|
FPrevX := col;
|
|
|
|
end else
|
|
|
|
col := FPrevX;
|
|
|
|
|
|
|
|
s := GetFieldValue(AFields, 'Y');
|
|
|
|
if (s <> '') and TryStrToInt(s, row) then begin
|
|
|
|
dec(row);
|
|
|
|
FPrevY := row;
|
|
|
|
end else
|
|
|
|
row := FPrevY;
|
|
|
|
|
|
|
|
if (row >= FNumRows) then begin
|
|
|
|
FWorkbook.AddErrorMsg('line %d, %s": column is outside range.', [FLineNumber, FRecordLine]);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
if (col >= FNumCols) then begin
|
|
|
|
FWorkbook.AddErrorMsg('line%d, "%s": row is outside range.', [FLineNumber, FRecordLine]);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
2018-06-12 20:06:46 +00:00
|
|
|
cell := sheet.GetCell(row, col);
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
// Formula
|
|
|
|
expr := GetFieldValue(AFields, 'E'); // expression in R1C1 syntax
|
|
|
|
if expr <> '' then
|
2019-07-31 21:29:07 +00:00
|
|
|
sheet.WriteFormula(cell, expr, false, true);
|
|
|
|
|
|
|
|
book.LockFormulas; // Protect formulas from being deleted by the WriteXXXX calls
|
|
|
|
try
|
|
|
|
// Value
|
|
|
|
sval := GetFieldValue(AFields, 'K');
|
|
|
|
if sval <> '' then begin
|
|
|
|
if sval[1] = '"' then
|
|
|
|
begin
|
|
|
|
sval := UnquoteStr(sval);
|
|
|
|
if (sval = 'TRUE') or (sval = 'FALSE') then
|
|
|
|
sheet.WriteBoolValue(cell, (sval = 'TRUE'))
|
|
|
|
else
|
|
|
|
sheet.WriteText(cell, UnquoteStr(sval))
|
|
|
|
// to do: error values
|
|
|
|
end else begin
|
|
|
|
val := StrToFloat(sval, FPointSeparatorSettings);
|
|
|
|
sheet.WriteNumber(cell, val);
|
|
|
|
// to do: dates
|
|
|
|
end;
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
2019-07-31 21:29:07 +00:00
|
|
|
finally
|
|
|
|
book.UnlockFormulas;
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKReader.ProcessFormat(const AFields: TsSYLKFields);
|
|
|
|
var
|
|
|
|
cell: PCell;
|
|
|
|
s, scol, srow, sval, scol1, scol2: String;
|
|
|
|
col, row, col1, col2: LongInt;
|
|
|
|
ch1, ch2: Char;
|
|
|
|
nf: TsNumberFormat;
|
2019-08-01 09:55:03 +00:00
|
|
|
nfs: String;
|
2016-10-20 21:21:08 +00:00
|
|
|
decs: Integer;
|
|
|
|
ha: TsHorAlignment;
|
2019-08-01 09:55:03 +00:00
|
|
|
hasStyle: Boolean;
|
|
|
|
fill: Boolean;
|
2016-10-20 21:21:08 +00:00
|
|
|
val: Double;
|
|
|
|
P: PChar;
|
2019-08-01 09:55:03 +00:00
|
|
|
n: Integer;
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet: TsWorksheet;
|
2019-08-01 09:55:03 +00:00
|
|
|
dest: Integer; // 0=cell format, 1=col format, 2=rowFormat
|
|
|
|
fmt: TsCellFormat;
|
|
|
|
fmtIdx: Integer;
|
|
|
|
b: TsCellBorders;
|
|
|
|
i: Integer;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet := FWorksheet as TsWorksheet;
|
|
|
|
|
2016-10-20 21:21:08 +00:00
|
|
|
nf := nfGeneral;
|
|
|
|
ha := haDefault;
|
|
|
|
decs := 0;
|
2019-08-01 09:55:03 +00:00
|
|
|
fill := false;
|
|
|
|
b := [];
|
|
|
|
InitFormatRecord(fmt);
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
// Format
|
2019-08-01 09:55:03 +00:00
|
|
|
nfs := '';
|
|
|
|
s := GetFieldValue(AFields, 'P');
|
|
|
|
if (s <> '') then begin
|
|
|
|
if LowerCase(s) = 'general' then
|
|
|
|
nf := nfGeneral
|
|
|
|
else if TryStrToInt(s, n) then
|
|
|
|
nfs := FNumFormatList[n];
|
|
|
|
end;
|
|
|
|
|
2016-10-20 21:21:08 +00:00
|
|
|
s := GetFieldValue(AFields, 'F');
|
|
|
|
if s <> '' then
|
|
|
|
begin
|
|
|
|
ch1 := s[1];
|
|
|
|
ch2 := s[Length(s)];
|
|
|
|
sval := copy(s, 2, Length(s));
|
|
|
|
|
|
|
|
// Number format
|
|
|
|
case ch1 of
|
|
|
|
'D': nf := nfGeneral;
|
|
|
|
'C': nf := nfCurrency;
|
|
|
|
'E': nf := nfExp;
|
|
|
|
'F': nf := nfFixed;
|
|
|
|
'G': nf := nfGeneral;
|
|
|
|
'$': ; // no idea what this is
|
|
|
|
'*': ; // no idea what this is
|
|
|
|
'%': nf := nfPercentage;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Decimal places
|
|
|
|
TryStrtoInt(sval, decs);
|
|
|
|
|
|
|
|
// Horizontal alignment
|
|
|
|
case ch2 of
|
|
|
|
'D': ha := haDefault;
|
|
|
|
'C': ha := haCenter;
|
|
|
|
'G': ; // "Standard" ???
|
|
|
|
'L': ha := haLeft;
|
|
|
|
'R': ha := haRight;
|
|
|
|
'-': ; // ???
|
|
|
|
end;
|
2019-08-01 09:55:03 +00:00
|
|
|
end;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
// Style
|
|
|
|
s := GetFieldValue(AFields, 'S');
|
|
|
|
if s <> '' then begin
|
|
|
|
for i := 1 to Length(s) do begin
|
|
|
|
ch1 := s[i];
|
|
|
|
case ch1 of
|
|
|
|
'S': fill := true;
|
|
|
|
'T': Include(b, cbNorth);
|
|
|
|
'L': Include(b, cbWest);
|
|
|
|
'R': Include(b, cbEast);
|
|
|
|
'B': Include(b, cbSouth);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
// Determin to which cell, column or row the format applies
|
|
|
|
dest := 0; // assume cell format
|
|
|
|
scol := GetFieldValue(AFields, 'X');
|
|
|
|
if (scol <> '') and TryStrToInt(scol, col) then begin
|
|
|
|
dec(col);
|
|
|
|
FPrevX := col;
|
|
|
|
end else
|
|
|
|
col := FPrevX;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
srow := GetFieldValue(AFields, 'Y');
|
|
|
|
if (srow <> '') and TryStrToInt(srow, row) then begin
|
|
|
|
dec(row);
|
|
|
|
FPrevY := row;
|
|
|
|
end else
|
|
|
|
row := FPrevY;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
if (row >= FNumRows) and (FNumRows > 0) then begin
|
|
|
|
FWorkbook.AddErrorMsg('line %d, %s": row %d is outside range.', [FLineNumber, FRecordLine, row]);
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
if (col >= FNumCols) and (FNumCols > 0) then begin
|
|
|
|
FWorkbook.AddErrorMsg('line%d, "%s": column %d is outside range.', [FLineNumber, FRecordLine, col]);
|
|
|
|
exit;
|
|
|
|
end;
|
2019-07-31 22:43:49 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
// Column format
|
|
|
|
scol := GetFieldValue(AFields, 'C');
|
|
|
|
if (scol <> '') and TryStrToInt(scol, col) then begin
|
|
|
|
dec(col);
|
|
|
|
dest := 1; // is column format
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Row format
|
|
|
|
srow := GetFieldValue(AFields, 'R');
|
|
|
|
if (srow <> '') and TryStrToInt(srow, row) then begin
|
|
|
|
dec(row);
|
|
|
|
dest := 2; // is row format
|
|
|
|
end;
|
2019-07-31 22:43:49 +00:00
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
// Create format record and store in workbook format list
|
|
|
|
if ha <> haDefault then begin
|
|
|
|
fmt.HorAlignment := ha;
|
|
|
|
Include(fmt.UsedFormattingfields, uffHorAlign);
|
|
|
|
end;
|
|
|
|
if fill then begin
|
|
|
|
fmt.Background.Style := fsGray25;
|
|
|
|
fmt.Background.FgColor := scBlack;
|
|
|
|
fmt.Background.BgColor := scTransparent;
|
|
|
|
Include(fmt.UsedFormattingFields, uffBackground);
|
|
|
|
end;
|
|
|
|
if b <> [] then begin
|
|
|
|
fmt.Border := b;
|
|
|
|
Include(fmt.UsedFormattingFields, uffBorder);
|
|
|
|
end;
|
|
|
|
if (nfs = '') then begin
|
|
|
|
if nf in [nfCurrency, nfCurrencyRed] then
|
|
|
|
nfs := BuildCurrencyFormatString(nf, FWorkbook.FormatSettings, decs, -1, -1, '?', false)
|
|
|
|
else
|
|
|
|
nfs := BuildNumberFormatString(nf, FWorkbook.FormatSettings, decs);
|
|
|
|
end;
|
|
|
|
if (nfs <> '') and (nfs <> 'General') then begin
|
|
|
|
fmt.NumberFormatIndex := TsWorkbook(FWorkbook).AddNumberFormat(nfs);
|
|
|
|
Include(fmt.UsedFormattingFields, uffNumberFormat);
|
|
|
|
end;
|
|
|
|
fmtIdx := TsWorkbook(FWorkbook).AddCellFormat(fmt);
|
|
|
|
|
|
|
|
// Apply format to cell, col or row
|
|
|
|
case dest of
|
|
|
|
0: begin
|
|
|
|
cell := sheet.GetCell(row, col);
|
|
|
|
sheet.WriteCellFormatIndex(cell, fmtIdx);
|
|
|
|
end;
|
|
|
|
1: sheet.WriteColFormatIndex(col, fmtIdx);
|
|
|
|
2: sheet.WriteRowFormatIndex(row, fmtIdx);
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// Column width
|
|
|
|
s := GetFieldValue(AFields, 'W');
|
|
|
|
if s <> '' then
|
|
|
|
begin
|
|
|
|
scol1 := '';
|
|
|
|
P := @s[1];
|
|
|
|
while P^ <> ' ' do begin
|
|
|
|
scol1 := scol1 + P^;
|
|
|
|
inc(P);
|
|
|
|
end;
|
|
|
|
inc(P);
|
|
|
|
scol2 := '';
|
|
|
|
while (P^ <> ' ') do begin
|
|
|
|
scol2 := scol2 + P^;
|
|
|
|
inc(P);
|
|
|
|
end;
|
|
|
|
inc(P);
|
|
|
|
sval := '';
|
|
|
|
while (P^ <> #0) do begin
|
|
|
|
sval := sval + P^;
|
|
|
|
inc(P);
|
|
|
|
end;
|
|
|
|
if TryStrToInt(scol1, col1) and
|
|
|
|
TryStrToInt(scol2, col2) and
|
|
|
|
TryStrToFloat(sval, val, FPointSeparatorSettings) then
|
|
|
|
begin
|
2019-07-31 22:43:49 +00:00
|
|
|
if col2 > FNumCols then
|
|
|
|
col2 := FNumCols;
|
2016-10-20 21:21:08 +00:00
|
|
|
for col := col1-1 to col2-1 do
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet.WriteColWidth(col, val, suChars);
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKReader.ProcessLine(const ALine: String);
|
|
|
|
var
|
|
|
|
P: PChar;
|
|
|
|
i: Integer;
|
|
|
|
rtd, fval: String;
|
|
|
|
ftd: Char;
|
|
|
|
fields: TsSYLKFields;
|
|
|
|
|
|
|
|
procedure StoreField(AName: Char; const AValue: String);
|
|
|
|
begin
|
|
|
|
if i >= Length(fields) then SetLength(fields, Length(fields)+100);
|
|
|
|
fields[i].Name := AName;
|
|
|
|
fields[i].Value := AValue;
|
|
|
|
inc(i);
|
|
|
|
end;
|
|
|
|
|
|
|
|
begin
|
|
|
|
// Get record type
|
|
|
|
rtd := '';
|
|
|
|
P := @ALine[1];
|
|
|
|
while (P^ <> ';') do begin
|
|
|
|
rtd := rtd + P^;
|
|
|
|
inc(P);
|
|
|
|
end;
|
|
|
|
inc(P);
|
|
|
|
|
|
|
|
if rtd = 'C' then
|
|
|
|
ftd := 'C';
|
|
|
|
|
|
|
|
// Get fields
|
|
|
|
SetLength(fields, 100);
|
|
|
|
i := 0;
|
|
|
|
while (P^ <> #0) do begin
|
|
|
|
ftd := P^;
|
|
|
|
inc(P);
|
|
|
|
fval := '';
|
|
|
|
while (P^ <> #0) do begin
|
|
|
|
case P^ of
|
|
|
|
';' : begin
|
|
|
|
inc(P);
|
|
|
|
if P^ = ';' then begin
|
|
|
|
fval := fval + P^;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
StoreField(ftd, fval);
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
else fval := fval + P^;
|
|
|
|
inc(P);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
if fval <> '' then
|
|
|
|
StoreField(ftd, fval);
|
|
|
|
|
|
|
|
// Process record
|
|
|
|
SetLength(fields, i);
|
|
|
|
ProcessRecord(rtd, fields);
|
|
|
|
end;
|
|
|
|
|
2019-08-01 09:55:03 +00:00
|
|
|
procedure TsSYLKReader.ProcessNumFormat(const AFields: TsSYLKFields);
|
|
|
|
var
|
|
|
|
s: String;
|
|
|
|
begin
|
|
|
|
s := GetFieldValue(AFields, 'P');
|
|
|
|
FNumFormatList.Add(s);
|
|
|
|
end;
|
|
|
|
|
2016-10-20 21:21:08 +00:00
|
|
|
procedure TsSYLKReader.ProcessRecord(ARecordType: String;
|
|
|
|
const AFields: TsSYLKFields);
|
|
|
|
begin
|
|
|
|
case ARecordType of
|
2019-08-01 09:55:03 +00:00
|
|
|
'ID': ; // Begin of file - nothing to do for us
|
|
|
|
'B' : ProcessBounds(AFields); // Bounds of the sheet
|
|
|
|
'C' : ProcessCell(AFields); // Content record
|
|
|
|
'F' : ProcessFormat(AFields); // Format record
|
|
|
|
'P' : ProcessNumFormat(AFields); // Excel number format
|
|
|
|
'E' : ; // End of file
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKReader.ReadFromFile(AFileName: String;
|
2017-03-19 23:10:55 +00:00
|
|
|
APassword: String = ''; AParams: TsStreamParams = []);
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
|
|
|
FWorksheetName := ChangeFileExt(ExtractFileName(AFileName), '');
|
2017-03-19 23:10:55 +00:00
|
|
|
inherited ReadFromFile(AFilename, APassword, AParams);
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKReader.ReadFromStrings(AStrings: TStrings;
|
|
|
|
AParams: TsStreamParams = []);
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
|
|
Unused(AParams);
|
2019-08-01 09:55:03 +00:00
|
|
|
FNumFormatList.Clear;
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
// Create worksheet
|
2018-06-12 20:06:46 +00:00
|
|
|
FWorksheet := (FWorkbook as TsWorkbook).AddWorksheet(FWorksheetName, true);
|
2016-10-20 21:21:08 +00:00
|
|
|
|
2019-07-31 22:43:49 +00:00
|
|
|
for i:=0 to AStrings.Count-1 do begin
|
|
|
|
FRecordLine := AStrings[i];
|
|
|
|
FLineNumber := i;
|
2016-10-20 21:21:08 +00:00
|
|
|
ProcessLine(AStrings[i]);
|
2019-07-31 22:43:49 +00:00
|
|
|
end;
|
2016-10-20 21:21:08 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{==============================================================================}
|
|
|
|
{ TsSYLKWriter }
|
|
|
|
{==============================================================================}
|
|
|
|
|
2018-06-12 20:06:46 +00:00
|
|
|
constructor TsSYLKWriter.Create(AWorkbook: TsBasicWorkbook);
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
|
|
|
FDateMode := SYLKSettings.DateMode;
|
|
|
|
FSheetIndex := SYLKSettings.SheetIndex;
|
|
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsSYLKWriter.GetFormatStr(ACell: PCell): String;
|
|
|
|
var
|
|
|
|
cellFmt: PsCellFormat;
|
|
|
|
ch1, ch2: Char;
|
|
|
|
decs: String;
|
|
|
|
nfp: TsNumFormatParams;
|
|
|
|
style: String;
|
|
|
|
fnt: TsFont;
|
2018-06-12 20:06:46 +00:00
|
|
|
wkBook: TsWorkbook;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
|
|
|
Result := '';
|
2018-06-12 20:06:46 +00:00
|
|
|
|
|
|
|
wkBook := FWorkbook as TsWorkbook;
|
|
|
|
|
|
|
|
cellFmt := wkBook.GetPointerToCellFormat(ACell^.FormatIndex);
|
2016-10-20 21:21:08 +00:00
|
|
|
if cellFmt <> nil then
|
|
|
|
begin
|
|
|
|
// Number format --> field ";P"
|
|
|
|
ch1 := 'G'; // general number format
|
|
|
|
decs := '0'; // decimal places
|
|
|
|
if (uffNumberFormat in cellFmt^.UsedFormattingFields) then begin
|
|
|
|
Result := Result + Format(';P%d', [cellFmt^.NumberFormatIndex+1]); // +1 because of General format not in list
|
2018-06-12 20:06:46 +00:00
|
|
|
nfp := wkBook.GetNumberFormat(cellFmt^.NumberFormatIndex);
|
2016-10-20 21:21:08 +00:00
|
|
|
case nfp.Sections[0].NumFormat of
|
|
|
|
nfFixed : ch1 := 'F';
|
|
|
|
nfCurrency : ch1 := 'C';
|
|
|
|
nfPercentage : ch1 := '%';
|
|
|
|
nfExp : ch1 := 'E';
|
|
|
|
else ch1 := 'G';
|
|
|
|
end;
|
|
|
|
decs := IntToStr(nfp.Sections[0].Decimals);
|
|
|
|
end else
|
|
|
|
Result := Result + ';P0';
|
|
|
|
|
|
|
|
// Horizontal alignment + old-style number format --> field ";F"
|
|
|
|
ch2 := 'D'; // default alignment
|
|
|
|
if (uffHorAlign in cellFmt^.UsedFormattingFields) then
|
|
|
|
case cellFmt^.HorAlignment of
|
|
|
|
haLeft : ch2 := 'L';
|
|
|
|
haCenter: ch2 := 'C';
|
|
|
|
haRight : ch2 := 'R';
|
|
|
|
end;
|
|
|
|
Result := Result + ';F' + ch1 + decs + ch2;
|
|
|
|
|
|
|
|
// Font style, Borders, background --> field ";S"
|
|
|
|
style := '';
|
|
|
|
if (uffFont in cellFmt^.UsedFormattingFields) then
|
|
|
|
begin
|
2018-06-12 20:06:46 +00:00
|
|
|
fnt := wkBook.GetFont(cellFmt^.FontIndex);
|
2016-10-20 21:21:08 +00:00
|
|
|
if (fssBold in fnt.Style) then style := style + 'D';
|
|
|
|
if (fssItalic in fnt.Style) then style := style + 'I';
|
|
|
|
end;
|
|
|
|
if (uffBorder in cellFmt^.UsedFormattingFields) then
|
|
|
|
begin
|
|
|
|
if (cbWest in cellFmt^.Border) then style := style + 'L';
|
|
|
|
if (cbEast in cellFmt^.Border) then style := style + 'R';
|
|
|
|
if (cbNorth in cellFmt^.Border) then style := style + 'T';
|
|
|
|
if (cbSouth in cellFmt^.Border) then style := style + 'B';
|
|
|
|
end;
|
|
|
|
if (uffBackground in cellFmt^.UsedFormattingFields) then
|
|
|
|
style := style + 'S';
|
|
|
|
|
|
|
|
if style <> '' then
|
|
|
|
Result := Result + ';S' + style;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Result := 'F' + Result + Format(';Y%d;X%d', [ACell^.Row+1, ACell^.Col+1]);
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsSYLKWriter.GetFormulaStr(ACell: PCell): String;
|
|
|
|
begin
|
|
|
|
if HasFormula(ACell) then
|
2018-06-12 20:06:46 +00:00
|
|
|
Result := ';E' + (FWorksheet as TsWorksheet).ConvertFormulaDialect(ACell, fdExcelR1C1)
|
|
|
|
else
|
2016-10-20 21:21:08 +00:00
|
|
|
Result := '';
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes a boolean value.
|
|
|
|
In the first line, we write the format code -- see GetFormatStr
|
|
|
|
In the second line, we write a "C" record containing the fields
|
|
|
|
- ";X" cell column index (1-based)
|
|
|
|
- ";Y" cell row index (1-based)
|
|
|
|
- ";K" boolean value as TRUE or FALSE, no quotes
|
|
|
|
- ";E" formula in R1C1 syntax, if available -- see GetFormulaStr
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: Boolean; ACell: PCell);
|
|
|
|
const
|
|
|
|
BOOLSTR: Array[boolean] of String = ('FALSE', 'TRUE');
|
|
|
|
var
|
|
|
|
sval: String;
|
|
|
|
sfmt: String;
|
|
|
|
begin
|
|
|
|
// Format codes
|
|
|
|
sfmt := GetFormatStr(ACell);
|
|
|
|
if sfmt <> '' then
|
|
|
|
sfmt := sfmt + LineEnding;
|
|
|
|
|
|
|
|
// Cell coordinates, value, formula
|
|
|
|
sval := Format('C;Y$d;X%d;K%s', [ARow+1, ACol+1, BOOLSTR[AValue]]) + GetFormulaStr(ACell);
|
|
|
|
|
|
|
|
// Write out
|
|
|
|
AppendToStream(AStream, sval + sfmt + LineEnding);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKWriter.WriteCellToStream(AStream: TStream; ACell: PCell);
|
|
|
|
begin
|
|
|
|
case ACell^.ContentType of
|
|
|
|
cctBool:
|
|
|
|
WriteBool(AStream, ACell^.Row, ACell^.Col, ACell^.BoolValue, ACell);
|
|
|
|
cctDateTime:
|
|
|
|
WriteDateTime(AStream, ACell^.Row, ACell^.Col, ACell^.DateTimeValue, ACell);
|
|
|
|
cctEmpty:
|
|
|
|
WriteBlank(AStream, ACell^.Row, ACell^.Col, ACell);
|
|
|
|
cctError:
|
|
|
|
WriteError(AStream, ACell^.Row, ACell^.Col, ACell^.ErrorValue, ACell);
|
|
|
|
cctNumber:
|
|
|
|
WriteNumber(AStream, ACell^.Row, ACell^.Col, ACell^.NumberValue, ACell);
|
|
|
|
cctUTF8String:
|
|
|
|
WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell);
|
|
|
|
end;
|
2018-06-12 20:06:46 +00:00
|
|
|
if (FWorksheet as TsWorksheet).HasComment(ACell) then
|
2016-10-20 21:21:08 +00:00
|
|
|
WriteComment(AStream, ACell);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes a comment record. This is a "C" record containing the fields
|
|
|
|
- ";X" cell column index (1-based)
|
|
|
|
- ";Y" cell row index (1-based)
|
|
|
|
- ";A" comment text, not quoted
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteComment(AStream: TStream; ACell: PCell);
|
|
|
|
var
|
|
|
|
comment: String;
|
|
|
|
begin
|
2018-06-12 20:06:46 +00:00
|
|
|
comment := (FWorksheet as TsWorksheet).ReadComment(ACell);
|
2016-10-20 21:21:08 +00:00
|
|
|
if comment <> '' then
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'C;Y%d;X%d;A%s' + LineEnding, [ACell^.Row+1, ACell^.Col+1, comment]));
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes a date/time value. The date/time cell is just an ordinary number cell,
|
|
|
|
just formatted with a date/time format.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: TDateTime; ACell: PCell);
|
|
|
|
var
|
|
|
|
DateSerial: double;
|
|
|
|
begin
|
|
|
|
DateSerial := ConvertDateTimeToExcelDateTime(AValue, FDateMode);
|
|
|
|
WriteNumber(AStream, ARow, ACol, DateSerial, ACell);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes out the size of the worksheet (row and column count)
|
|
|
|
In SYLK, this is a "B" record followed by the fields ";Y" and ";X" containing
|
|
|
|
the row and column counts.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteDimensions(AStream: TStream);
|
2018-06-12 20:06:46 +00:00
|
|
|
var
|
|
|
|
sheet: TsWorksheet;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet := FWorksheet as TsWorksheet;
|
2016-10-20 21:21:08 +00:00
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'B;Y%d;X%d;D%d %d %d %d' + LineEnding, [
|
2018-06-12 20:06:46 +00:00
|
|
|
sheet.GetLastRowIndex+1, sheet.GetLastColIndex+1,
|
|
|
|
sheet.GetFirstRowIndex, sheet.GetFirstColIndex,
|
|
|
|
sheet.GetLastRowIndex, sheet.GetLastColIndex
|
2016-10-20 21:21:08 +00:00
|
|
|
]));
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes out an "E" record which is the last record of a SYLK file
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteEndOfFile(AStream: TStream);
|
|
|
|
begin
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'E' + LineEnding);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKWriter.WriteHeader(AStream: TStream);
|
|
|
|
begin
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'ID;PFPS' + LineEnding); // ID + generating app ("FPS" = FPSpreadsheet)
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes a text value.
|
|
|
|
In the first line, we write the format code -- see GetFormatStr
|
|
|
|
In the second line, we write a "C" record containing the fields
|
|
|
|
- ";X" cell column index (1-based)
|
|
|
|
- ";Y" cell row index (1-based)
|
|
|
|
- ";K" text value in double quotes
|
|
|
|
- ";E" formula in R1C1 syntax, if available -- see GetFormulaStr
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: String; ACell: PCell);
|
|
|
|
var
|
|
|
|
sval: String;
|
|
|
|
sfmt: String;
|
|
|
|
begin
|
|
|
|
// Format codes
|
|
|
|
sfmt := GetFormatStr(ACell);
|
|
|
|
if sfmt <> '' then
|
|
|
|
sfmt := sfmt + LineEnding;
|
|
|
|
|
|
|
|
// Cell coordinates, value, formula
|
|
|
|
sval := Format('C;Y%d;X%d;K"%s"', [ARow+1, ACol+1, AValue]) + GetFormulaStr(ACell);
|
|
|
|
|
|
|
|
// Write out
|
|
|
|
AppendToStream(AStream, sfmt + sval + LineEnding);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes a number value.
|
|
|
|
In the first line, we write the format code -- see GetFormatStr
|
|
|
|
In the second line, we write a "C" record containing the fields
|
|
|
|
- ";X" cell column index (1-based)
|
|
|
|
- ";Y" cell row index (1-based)
|
|
|
|
- ";K" number value as unformatted string
|
|
|
|
- ";E" formula in R1C1 syntax, if available -- see GetFormulaStr
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: double; ACell: PCell);
|
|
|
|
var
|
|
|
|
sval: String;
|
|
|
|
sfmt: String;
|
|
|
|
begin
|
|
|
|
// Format codes
|
|
|
|
sfmt := GetFormatStr(ACell);
|
|
|
|
if sfmt <> '' then
|
|
|
|
sfmt := sfmt + LineEnding;
|
|
|
|
|
|
|
|
// Cell coordinates, value, formula
|
|
|
|
sval := Format('C;Y%d;X%d;K%g', [ARow+1, ACol+1, AValue], FPointSeparatorSettings);
|
|
|
|
sval := sval + GetFormulaStr(ACell);
|
|
|
|
|
|
|
|
// Write out
|
|
|
|
AppendToStream(AStream, sfmt + sval + LineEnding);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Writes the list of number formats.
|
|
|
|
In SYLK, this is a sequence of "P" records. Each record contains the Excel
|
|
|
|
format string with field identifier ";P"
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure TsSYLKWriter.WriteNumberFormatList(AStream: TStream);
|
|
|
|
var
|
|
|
|
nfp: TsNumFormatParams;
|
|
|
|
nfs: String;
|
|
|
|
i, j: Integer;
|
2018-06-12 20:06:46 +00:00
|
|
|
wkbook: TsWorkbook;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
2018-06-12 20:06:46 +00:00
|
|
|
wkbook := FWorkbook as TsWorkbook;
|
|
|
|
|
2016-10-20 21:21:08 +00:00
|
|
|
AppendToStream(AStream,
|
|
|
|
'P;PGeneral' + LineEnding);
|
|
|
|
|
2018-06-12 20:06:46 +00:00
|
|
|
for i:=0 to wkBook.GetNumberFormatCount-1 do begin
|
|
|
|
nfp := wkBook.GetNumberFormat(i);
|
2016-10-20 21:21:08 +00:00
|
|
|
nfs := BuildFormatStringFromSection(nfp.Sections[0]);
|
|
|
|
for j:=1 to High(nfp.Sections) do
|
|
|
|
nfs := nfs + ';;' + BuildFormatStringFromSection(nfp.Sections[j]);
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'P;P' + nfs + LineEnding);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKWriter.WriteOptions(AStream: TStream);
|
|
|
|
var
|
|
|
|
dateModeStr: String;
|
|
|
|
A1ModeStr: String;
|
|
|
|
begin
|
|
|
|
A1ModeStr := ';L'; // Display formulas in A1 mode.
|
|
|
|
|
|
|
|
case FDateMode of // Datemode 1900 or 1904
|
|
|
|
dm1900: dateModeStr := ';V0';
|
|
|
|
dm1904: dateModeStr := ';V4';
|
|
|
|
end;
|
|
|
|
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'O' + A1ModeStr + dateModeStr + LineEnding
|
|
|
|
);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSYLKWriter.WriteToStream(AStream: TStream;
|
|
|
|
AParams: TsStreamParams = []);
|
2018-06-12 20:06:46 +00:00
|
|
|
var
|
|
|
|
wkBook: TsWorkbook;
|
2016-10-20 21:21:08 +00:00
|
|
|
begin
|
|
|
|
Unused(AParams);
|
2018-06-12 20:06:46 +00:00
|
|
|
wkbook := FWorkbook as TsWorkbook;
|
|
|
|
|
|
|
|
if (FSheetIndex < 0) or (FSheetIndex >= wkBook.GetWorksheetCount) then
|
2016-10-20 21:21:08 +00:00
|
|
|
raise Exception.Create('[TsSYLKWriter.WriteToStream] Non-existing worksheet.');
|
|
|
|
|
2018-06-12 20:06:46 +00:00
|
|
|
FWorksheet := wkBook.GetWorksheetByIndex(FSheetIndex);
|
2016-10-20 21:21:08 +00:00
|
|
|
|
|
|
|
WriteHeader(AStream);
|
|
|
|
WriteNumberFormatList(AStream);
|
|
|
|
WriteDimensions(AStream);
|
|
|
|
WriteOptions(AStream);
|
2018-06-12 20:06:46 +00:00
|
|
|
WriteCellsToStream(AStream, (FWorksheet as TsWorksheet).Cells);
|
2016-10-20 21:21:08 +00:00
|
|
|
WriteEndOfFile(AStream);
|
|
|
|
end;
|
|
|
|
|
|
|
|
initialization
|
|
|
|
|
|
|
|
sfidSYLK := RegisterSpreadFormat(sfUser,
|
|
|
|
TsSYLKReader, TsSYLKWriter,
|
|
|
|
STR_FILEFORMAT_SYLK, 'SYLK', ['.slk', '.sylk']
|
|
|
|
);
|
|
|
|
|
|
|
|
end.
|
|
|
|
|