You've already forked lazarus-ccr
applications
bindings
components
Comba_Animation
aboutcomponent
acs
beepfp
callite
captcha
chelper
chemtext
cmdline
cmdlinecfg
colorpalette
cryptini
csvdocument
epiktimer
everettrandom
examplecomponent
exctrls
extrasyn
fpexif
fpsound
fpspreadsheet
docs
examples
images
languages
reference
resource
source
common
fpolebasic.pas
fpolestorage.pas
fpsallformats.pas
fpscell.pas
fpsclasses.pas
fpsconditionalformat.pas
fpscrypto.pas
fpscsv.pas
fpscsvdocument.pas
fpscurrency.pas
fpsexprparser.pas
fpsfunc.pas
fpsheaderfooterparser.pas
fpshtml.pas
fpshtmlutils.pas
fpsimages.pas
fpsnumformat.pas
fpsopendocument.pas
fpspagelayout.pas
fpspalette.pas
fpspatches.pas
fpspreadsheet.pas
fpspreadsheet_cf.inc
fpspreadsheet_clipbrd.inc
fpspreadsheet_comments.inc
fpspreadsheet_embobj.inc
fpspreadsheet_fmt.inc
fpspreadsheet_fonts.inc
fpspreadsheet_hyperlinks.inc
fpspreadsheet_numfmt.inc
fpsreaderwriter.pas
fpsrpn.pas
fpssearch.pas
fpsstreams.pas
fpsstringhashlist.pas
fpsstrings.pas
fpstypes.pas
fpsutils.pas
fpsxmlcommon.pas
fpszipper.pp
uvirtuallayer.pas
uvirtuallayer_ole.pas
uvirtuallayer_ole_helpers.pas
uvirtuallayer_ole_types.pas
uvirtuallayer_stream.pas
uvirtuallayer_types.pas
wikitable.pas
xlsbiff2.pas
xlsbiff5.pas
xlsbiff8.pas
xlscommon.pas
xlsconst.pas
xlsescher.pas
xlsxml.pas
xlsxooxml.pas
crypto
dataset
design
export
visual
fps.inc
unit-tests
README.txt
install.txt
laz_fpspreadsheet.lpk
laz_fpspreadsheet_crypto.lpk
laz_fpspreadsheet_dataset.lpk
laz_fpspreadsheet_visual.lpk
laz_fpspreadsheet_visual_dsgn.lpk
laz_fpspreadsheetexport_visual.lpk
fractions
freetypepascal
geckoport
gradcontrols
grid_semaphor
industrialstuff
iosdesigner
iphonelazext
jujiboutils
jvcllaz
kcontrols
lazautoupdate
lazbarcodes
lazmapviewer
lclextensions
longtimer
manualdock
mbColorLib
mplayer
multithreadprocs
nvidia-widgets
onguard
orpheus
playsoundpackage
poweredby
powerpdf
rgbgraphics
richmemo
richview
rtfview
rx
scrolltext
smnetgradient
spktoolbar
splashabout
svn
systools
tdi
thtmlport
tparadoxdataset
tvplanit
xdev_toolkit
zlibar
zmsql
examples
image_sources
lclbindings
wst
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8126 8e941d3f-bd1b-0410-a28a-d453659cc2b4
3623 lines
118 KiB
ObjectPascal
3623 lines
118 KiB
ObjectPascal
{-------------------------------------------------------------------------------
|
|
Unit : xlsxml
|
|
|
|
Implements a reader and writer for the SpreadsheetXML format.
|
|
This document was introduced by Microsoft for Excel XP and 2003.
|
|
|
|
REFERENCE: http://msdn.microsoft.com/en-us/library/aa140066%28v=office.15%29.aspx
|
|
|
|
AUTHOR : Werner Pamler
|
|
|
|
LICENSE : For details about the license, see the file
|
|
COPYING.modifiedLGPL.txt included in the Lazarus distribution.
|
|
-------------------------------------------------------------------------------}
|
|
|
|
unit xlsxml;
|
|
|
|
{$ifdef fpc}
|
|
{$mode objfpc}{$H+}
|
|
{$endif}
|
|
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
laz2_xmlread, laz2_DOM,
|
|
fpsTypes, fpsReaderWriter, fpsConditionalFormat, fpsXMLCommon, xlsCommon;
|
|
|
|
type
|
|
{ TsSpreadExcelXMLReader }
|
|
TsSpreadExcelXMLReader = class(TsSpreadXMLReader)
|
|
private
|
|
FDateMode: TDateMode;
|
|
FPointSeparatorSettings: TFormatSettings;
|
|
function ExtractDateTime(AText: String): TDateTime;
|
|
|
|
protected
|
|
FFirstNumFormatIndexInFile: Integer;
|
|
procedure AddBuiltinNumFormats; override;
|
|
|
|
protected
|
|
procedure ReadAlignment(ANode: TDOMNode; var AFormat: TsCellFormat);
|
|
procedure ReadBorder(ANode: TDOMNode; var AFormat: TsCellFormat);
|
|
procedure ReadBorders(ANode: TDOMNode; var AFormat: TsCellFormat);
|
|
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 ReadConditionalFormatting(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
procedure ReadCustomDocumentProperties(ANode: TDOMNode);
|
|
procedure ReadDocumentProperties(ANode: TDOMNode);
|
|
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 ReadPageBreak(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
procedure ReadPageBreaks(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
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);
|
|
procedure ReadTable(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
procedure ReadWorksheetOptions(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
|
|
procedure ReadWorksheets(ANode: TDOMNode);
|
|
|
|
public
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
|
procedure ReadFromStream(AStream: TStream; APassword: String = '';
|
|
AParams: TsStreamParams = []); override;
|
|
end;
|
|
|
|
|
|
{ TsSpreadExcelXMLWriter }
|
|
|
|
TsSpreadExcelXMLWriter = class(TsCustomSpreadWriter)
|
|
private
|
|
FDateMode: TDateMode;
|
|
FPointSeparatorSettings: TFormatSettings;
|
|
FFirstRow, FFirstCol: Cardinal;
|
|
FlastRow, FLastCol: Cardinal;
|
|
FPrevRow, FPrevCol: Cardinal;
|
|
function GetCommentStr(ACell: PCell): String;
|
|
function GetFormulaStr(ACell: PCell): String;
|
|
function GetFrozenPanesStr(AWorksheet: TsBasicWorksheet; AIndent: String): String;
|
|
function GetHyperlinkStr(ACell: PCell): String;
|
|
function GetIndexStr(AIndex, APrevIndex: Cardinal): String;
|
|
function GetLayoutStr(AWorksheet: TsBasicWorksheet): String;
|
|
function GetMergeStr(ACell: PCell): String;
|
|
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 WriteCellNodes(AStream: TStream; AWorksheet: TsBasicWorksheet; ARow: Cardinal);
|
|
procedure WriteColumns(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteConditionalFormat(AStream: TStream; AWorksheet: TsBasicWorksheet;
|
|
AFormat: TsConditionalFormat);
|
|
procedure WriteConditionalFormatting(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteCustomDocumentProperties(AStream: TStream);
|
|
procedure WriteDocumentProperties(AStream: TStream);
|
|
procedure WriteExcelWorkbook(AStream: TStream);
|
|
procedure WriteNames(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteOfficeDocumentSettings(AStream: TStream);
|
|
procedure WritePageBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteRows(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteStyle(AStream: TStream; AIndex: Integer);
|
|
procedure WriteStyles(AStream: TStream);
|
|
procedure WriteTable(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteWorksheet(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteWorksheetOptions(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
|
procedure WriteWorksheets(AStream: TStream);
|
|
|
|
protected
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell); override;
|
|
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: boolean; ACell: PCell); override;
|
|
procedure WriteCellToStream(AStream: TStream; ACell: PCell); override;
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: TDateTime; ACell: PCell); override;
|
|
procedure WriteError(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: TsErrorValue; ACell: PCell); override;
|
|
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
ACell: PCell); override;
|
|
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;
|
|
|
|
public
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
|
procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); override;
|
|
|
|
end;
|
|
|
|
TExcelXmlSettings = record
|
|
DateMode: TDateMode;
|
|
end;
|
|
|
|
var
|
|
{ Default parameters for reading/writing }
|
|
ExcelXmlSettings: TExcelXmlSettings = (
|
|
DateMode: dm1900;
|
|
);
|
|
|
|
sfidExcelXML: TsSpreadFormatID;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
StrUtils, DateUtils, Math, Variants, TypInfo,
|
|
fpsStrings, fpsClasses, fpspreadsheet, fpsUtils, fpsNumFormat, fpsHTMLUtils,
|
|
fpsExprParser;
|
|
|
|
const
|
|
FMT_OFFSET = 61;
|
|
|
|
INDENT1 = ' ';
|
|
INDENT2 = ' ';
|
|
INDENT3 = ' ';
|
|
INDENT4 = ' ';
|
|
INDENT5 = ' ';
|
|
NAMES_INDENT = INDENT2;
|
|
NAME_INDENT = INDENT3;
|
|
TABLE_INDENT = INDENT2;
|
|
ROW_INDENT = INDENT3;
|
|
COL_INDENT = INDENT3;
|
|
CELL_INDENT = INDENT4;
|
|
VALUE_INDENT = INDENT5;
|
|
|
|
LF = LineEnding;
|
|
|
|
const
|
|
{TsFillStyle = (
|
|
fsNoFill, fsSolidFill,
|
|
fsGray75, fsGray50, fsGray25, fsGray12, fsGray6,
|
|
fsStripeHor, fsStripeVert, fsStripeDiagUp, fsStripeDiagDown,
|
|
fsThinStripeHor, fsThinStripeVert, fsThinStripeDiagUp, fsThinStripeDiagDown,
|
|
fsHatchDiag, fsThinHatchDiag, fsThickHatchDiag, fsThinHatchHor) }
|
|
FILL_NAMES: array[TsFillStyle] of string = (
|
|
'', 'Solid',
|
|
// 'Solid', 'Solid', 'Solid', 'Solid', 'Solid',
|
|
'Gray75', 'Gray50', 'Gray25', 'Gray125', 'Gray0625',
|
|
'HorzStripe', 'VertStripe', 'DiagStripe', 'ReverseDiagStripe',
|
|
'ThinHorzStripe', 'ThinVertStripe', 'ThinDiagStripe', 'ThinReverseDiagStripe',
|
|
'DiagCross', 'ThinDiagCross', 'ThickDiagCross', 'ThinHorzCross'
|
|
);
|
|
|
|
{ Fill style names as used in the Style attribute for conditional formatting -- not all tested... }
|
|
CF_FILL_NAMES: array[TsFillStyle] of string = (
|
|
'', 'solid',
|
|
'gray-75', 'gray-50', 'gray-25', 'gray-125', 'gray-0625',
|
|
'horz-stripe', 'vert-stripe', 'diag-stripe', 'reverse-diag-stripe',
|
|
'thin-horz-stripe', 'thin-vert-stripe', 'thin-diag-stripe', 'thin-reverse-diag-stripe',
|
|
'diag-cross', 'thin-diag-cross', 'thick-diag-cross', 'thin-horz-cross'
|
|
);
|
|
|
|
{TsCellBorder = (cbNorth, cbWest, cbEast, cbSouth, cbDiagUp, cbDiagDown); }
|
|
BORDER_NAMES: array[TsCellBorder] of string = (
|
|
'Top', 'Left', 'Right', 'Bottom', 'DiagonalRight', 'DiagonalLeft'
|
|
);
|
|
|
|
{TsLineStyle = (
|
|
lsThin, lsMedium, lsDashed, lsDotted, lsThick, lsDouble, lsHair,
|
|
lsMediumDash, lsDashDot, lsMediumDashDot, lsDashDotDot, lsMediumDashDotDot,
|
|
lsSlantDashDot) }
|
|
LINE_STYLES: array[TsLineStyle] of string = (
|
|
'Continuous', 'Continuous', 'Dash', 'Dot', 'Continuous', 'Double', 'Continuous',
|
|
'Dash', 'DashDot', 'DashDot', 'DashDotDot', 'DashDotDot',
|
|
'SlantDashDot'
|
|
);
|
|
|
|
CF_LINE_STYLES: array[TsLineStyle] of string = (
|
|
'solid', 'solid', 'dashed', 'dotted', 'solid', 'double', 'hairline',
|
|
'dashed', 'dot-dash', 'dot-dash', 'dot-dot-dash', 'dot-dot-dash',
|
|
'dot-dash'
|
|
);
|
|
|
|
LINE_WIDTHS: array[TsLineStyle] of Integer = (
|
|
1, 2, 1, 1, 3, 3, 0,
|
|
2, 1, 2, 1, 2,
|
|
2
|
|
);
|
|
|
|
FALSE_TRUE: array[boolean] of string = ('False', 'True');
|
|
|
|
CF_CONDITIONS: array[TsCFCondition] of string = (
|
|
'Equal', 'NotEqual', // cfcEqual, cfcNotEqual,
|
|
'Greater', 'Less', 'GreaterOrEqual', 'LessOrEqual', // cfcGreaterThan, cfcLessThan, cfcGreaterEqual, cfcLessEqual,
|
|
'Between', 'NotBetween', // cfcBetween, cfcNotBetween,
|
|
// the following 4 formulas are copies of Excel-generated files, they exist in the xmls file, but Excel does not display them...
|
|
'@RC>AVERAGE( IF(ISERROR(%2:s), "", IF(ISBLANK(%2:s), "", %2:s)))', // cfcAboveAverage
|
|
'@RC<AVERAGE( IF(ISERROR(%2:s), "", IF(ISBLANK(%2:s), "", %2:s)))', // cfcBelowAverage
|
|
'@RC>=AVERAGE( IF(ISERROR(%2:s), "", IF(ISBLANK(%2:s), "", %2:s)))', // cfcAboveEqualAverage
|
|
'@RC<=AVERAGE( IF(ISERROR(%2:s), "", IF(ISBLANK(%2:s), "", %2:s)))', // cfcBelowEqualAverage
|
|
// The next 4 formulas are not supported by Excel-XML
|
|
'', '', '', '', // cfcTop, cfcBottom, cfcTopPercent, cfcBottomPercent,
|
|
'@AND(COUNTIF(%2:s, RC)>1,NOT(ISBLANK(RC)))', // cfcDuplicate
|
|
'@AND(COUNTIF(%2:s, RC)=1,NOT(ISBLANK(RC)))', // cfcUnique
|
|
'@LEFT(RC,LEN(%0:s))=%0:s', // cfcBeginsWith
|
|
'@RIGHT(RC,LEN(%0:s))=%0:s', // cfcEndsWith
|
|
'@NOT(ISERROR(SEARCH(%0:s,RC)))', // cfcContainsText
|
|
'@ISERROR(SEARCH(%0:s,RC))', // cfcNotContainsText,
|
|
'@ISERROR(RC)', // cfcContainsErrors
|
|
'@NOT(ISERROR(RC))', // cfcNotContainsErrors
|
|
'@FLOOR(RC,1)=TODAY()-1', // cfcYesterday
|
|
'@FLOOR(RC,1)=TODAY()', // cfcToday
|
|
'@FLOOR(RC,1)=TODAY()+1', // cfcTomorrow
|
|
'@AND(TODAY()-FLOOR(RC,1)<=6,FLOOR(RC,1)<=TODAY())', // cfcLast7Days
|
|
'@AND(TODAY()-ROUNDDOWN(RC,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(RC,0)<(WEEKDAY(TODAY())+7))', // cfcLastWeek
|
|
'@AND(TODAY()-ROUNDDOWN(RC,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(RC,0)-TODAY()<=7-WEEKDAY(TODAY()))', // cfcThisWeek
|
|
'@AND(ROUNDDOWN(RC,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(RC,0)-TODAY()<(15-WEEKDAY(TODAY())))', // cfcNextWeek
|
|
'@AND(MONTH(RC)=MONTH(EDATE(TODAY(),0-1)),YEAR(RC)=YEAR(EDATE(TODAY(),0-1)))', // cfcLastMonth
|
|
'@AND(MONTH(RC)=MONTH(TODAY()),YEAR(RC)=YEAR(TODAY()))', // cfcThisMonth
|
|
'@AND(MONTH(RC)=MONTH(EDATE(TODAY(),0+1)),YEAR(RC)=YEAR(EDATE(TODAY(),0+1)))', // cfcNextMonth
|
|
'@YEAR(RC)=YEAR(TODAY())-1', // cfcLastYear
|
|
'@YEAR(RC)=YEAR(TODAY())', // cfcThisYear
|
|
'@YEAR(RC)=YEAR(TODAY())+1', // cfcNextYear
|
|
'@' // cfcExpression
|
|
);
|
|
// The leading '@' indicates that the formula will be used in <Value1> node
|
|
// Parameter 0 is Operand1, parameter 1 is Operand2 and parameter 2 is Range
|
|
|
|
function GetCellContentTypeStr(ACell: PCell): String;
|
|
begin
|
|
case ACell^.ContentType of
|
|
cctNumber : Result := 'Number';
|
|
cctUTF8String : Result := 'String';
|
|
cctDateTime : Result := 'DateTime';
|
|
cctBool : Result := 'Boolean';
|
|
cctError : Result := 'Error';
|
|
else
|
|
raise EFPSpreadsheet.Create('Content type error in cell ' + GetCellString(ACell^.Row, ACell^.Col));
|
|
end;
|
|
end;
|
|
|
|
{ Helper routine to rebuild the html content of the "ss:Data" nodes }
|
|
procedure RebuildChildNodes(ANode: TDOMNode; var AText: String);
|
|
var
|
|
nodeName: String;
|
|
s: String;
|
|
i: Integer;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = '#text' then
|
|
AText := AText + ANode.NodeValue
|
|
else begin
|
|
s := '';
|
|
for i := 0 to ANode.Attributes.Length-1 do
|
|
s := Format('%s %s="%s"', [s, ANode.Attributes.Item[i].NodeName, ANode.Attributes.Item[i].NodeValue]);
|
|
AText := Format('%s<%s%s>', [AText, nodeName, s]);
|
|
s := '';
|
|
RebuildChildNodes(ANode.FirstChild, s);
|
|
if s <> '' then
|
|
AText := Format('%s%s</%s>', [AText, s, nodeName]);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
function CFOperandToStr(v: variant; AWorksheet: TsWorksheet): String;
|
|
var
|
|
r,c: Cardinal;
|
|
parser: TsSpreadsheetParser;
|
|
begin
|
|
Result := VarToStr(v);
|
|
if Result = '' then
|
|
exit;
|
|
|
|
if VarIsStr(v) then begin
|
|
// Special case: v is a formula, i.e. begins with '='
|
|
if (Length(Result) > 1) and (Result[1] = '=') then
|
|
begin
|
|
parser := TsSpreadsheetParser.Create(AWorksheet);
|
|
try
|
|
try
|
|
parser.Expression[fdExcelA1] := Result; // Parse in Excel-A1 dialect
|
|
Result := parser.R1C1Expression[nil]; // Convert to R1C1 dialect
|
|
except
|
|
on EGeneralExprParserError do
|
|
begin
|
|
Result := VarToStr(v);
|
|
AWorksheet.Workbook.AddErrorMsg('Error in CF Expression ' + Result);
|
|
end;
|
|
end;
|
|
// Note: Using nil here to get absolute references.
|
|
finally
|
|
parser.Free;
|
|
end;
|
|
end
|
|
else
|
|
// Special case: cell reference (Note: relative refs are made absolute!)
|
|
if ParseCellString(Result, r, c) then
|
|
Result := GetCellString_R1C1(r, c, []) // Need absolute reference!
|
|
else
|
|
Result := UTF8TextToXMLText(SafeQuoteStr(Result))
|
|
end;
|
|
end;
|
|
|
|
function TryStrToCFLineStyle(s: String; out ALineStyle: TsLineStyle): Boolean;
|
|
var
|
|
ls: TsLineStyle;
|
|
begin
|
|
for ls in TsLineStyle do
|
|
if s = CF_LINE_STYLES[ls] then
|
|
begin
|
|
Result := true;
|
|
ALineStyle := ls;
|
|
exit;
|
|
end;
|
|
Result := false;
|
|
end;
|
|
|
|
function TryStrToCFCellBorder(s: String; out ABorder: TsCellBorder): Boolean;
|
|
begin
|
|
Result := true;
|
|
if s = 'border-left' then
|
|
ABorder := cbWest
|
|
else if s = 'border-right' then
|
|
Aborder := cbEast
|
|
else if s = 'border-top' then
|
|
ABorder := cbNorth
|
|
else if s = 'border-bottom' then
|
|
ABorder := cbSouth
|
|
else if s = 'border-diagonal-right' then // not tested !
|
|
ABorder := cbDiagUp
|
|
else if s = 'border-diagonal-left' then // not tested !
|
|
ABorder := cbDiagDown
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
{ Analyzes the given expression. Using the @ templates of CF_CONDITIONS it
|
|
determines the condition type as well as the parameters. }
|
|
procedure AnalyzeCFExpression(AExpr: String; out ACondition: TsCFCondition;
|
|
out AParam: String);
|
|
var
|
|
p, n: Integer;
|
|
c: TsCFCondition;
|
|
expr: String;
|
|
begin
|
|
AParam := '';
|
|
//AExpr := UTF8TextToXMLText(AExpr);
|
|
if pos('RC>AVERAGE(', AExpr) = 1 then
|
|
ACondition := cfcAboveAverage
|
|
else
|
|
if pos ('RC<AVERAGE(', AExpr) = 1 then
|
|
ACondition := cfcBelowAverage
|
|
else
|
|
if pos('RC>=AVERAGE(', AExpr) = 1 then
|
|
ACondition := cfcAboveEqualAverage
|
|
else
|
|
if pos('RC<=AVERAGE(', AExpr) = 1 then
|
|
ACondition := cfcBelowEqualAverage
|
|
else
|
|
if (pos('AND(COUNTIF(', AExpr) = 1) and (pos('>', AExpr) > 0) then
|
|
ACondition := cfcDuplicate
|
|
else
|
|
if (pos('AND(COUNTIF(', AExpr) = 1) and (pos('=1', AExpr) > 0) then
|
|
ACondition := cfcUnique
|
|
else
|
|
if pos('LEFT(RC,LEN(', AExpr) = 1 then
|
|
begin
|
|
ACondition := cfcBeginsWith;
|
|
p := pos(')', AExpr);
|
|
n := Length('LEFT(RC,LEN(');
|
|
AParam := UnquoteStr(Trim(Copy(AExpr, n+1, p-n-1)));
|
|
end else
|
|
if pos('RIGHT(RC,LEN(',AExpr) = 1 then
|
|
begin
|
|
ACondition := cfcEndsWith;
|
|
p := pos(')', AExpr);
|
|
n := Length('RIGHT(RC,LEN(');
|
|
AParam := UnquoteStr(Trim(Copy(AExpr, n+1, p-n-1)));
|
|
end else
|
|
if pos('NOT(ISERROR(SEARCH(', AExpr) = 1 then
|
|
begin
|
|
ACondition := cfcContainsText;
|
|
p := pos(',', AExpr);
|
|
n := Length('NOT(ISERROR(SEARCH(');
|
|
AParam := UnquoteStr(Trim(Copy(AExpr, n+1, p-n-1)));
|
|
end else
|
|
if pos('ISERROR(SEARCH(', AExpr) = 1 then
|
|
begin
|
|
ACondition := cfcNotContainsText;
|
|
p := pos(',', AExpr);
|
|
n := Length('ISERROR(SEARCH(');
|
|
AParam := UnquoteStr(Trim(Copy(AExpr, n+1, p-n-1)));
|
|
end else
|
|
begin
|
|
expr := '@' + UTF8TextToXMLText(AExpr);
|
|
for c in [cfcContainsErrors..cfcNextYear] do
|
|
if CF_CONDITIONS[c] = expr then
|
|
begin
|
|
ACondition := c;
|
|
exit;
|
|
end;
|
|
|
|
ACondition := cfcExpression;
|
|
AParam := AExpr;
|
|
end;
|
|
end;
|
|
|
|
|
|
{===============================================================================
|
|
TsSpreadExcelXMLReader
|
|
===============================================================================}
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Constructor of the ExcelXML reader
|
|
-------------------------------------------------------------------------------}
|
|
constructor TsSpreadExcelXMLReader.Create(AWorkbook: TsBasicWorkbook);
|
|
begin
|
|
inherited;
|
|
|
|
// Cell formats (named "Styles" here).
|
|
FCellFormatList := TsCellFormatList.Create(true); // is destroyed by ancestor
|
|
|
|
// Special version of FormatSettings using a point decimal separator for sure.
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLReader.AddBuiltinNumFormats;
|
|
begin
|
|
FFirstNumFormatIndexInFile := 164;
|
|
AddBuiltInBiffFormats(
|
|
FNumFormatList, FWorkbook.FormatSettings, FFirstNumFormatIndexInFile-1
|
|
);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Extracts the date/time value from the given string.
|
|
The string is formatted as 'yyyy-mm-dd"T"hh:nn:ss.zzz'
|
|
-------------------------------------------------------------------------------}
|
|
function TsSpreadExcelXMLReader.ExtractDateTime(AText: String): TDateTime;
|
|
var
|
|
dateStr, timeStr: String;
|
|
begin
|
|
dateStr := Copy(AText, 1, 10);
|
|
timeStr := Copy(AText, 12, MaxInt);
|
|
Result := ScanDateTime('yyyy-mm-dd', dateStr) + ScanDateTime('hh:nn:ss.zzz', timeStr);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the cell alignment from the given node attributes
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadAlignment(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
s: String;
|
|
begin
|
|
// Vertical alignment
|
|
s := GetAttrValue(ANode, 'ss:Vertical');
|
|
if s <> '' then
|
|
with AFormat do begin
|
|
Include(UsedFormattingFields, uffVertAlign);
|
|
case s of
|
|
'Top':
|
|
VertAlignment := vaTop;
|
|
'Center':
|
|
VertAlignment := vaCenter;
|
|
'Bottom':
|
|
VertAlignment := vaBottom;
|
|
else
|
|
Exclude(UsedFormattingFields, uffVertAlign);
|
|
end;
|
|
end;
|
|
|
|
// Horizontal alignment
|
|
s := GetAttrValue(ANode, 'ss:Horizontal');
|
|
if s <> '' then
|
|
with AFormat do begin
|
|
Include(UsedFormattingFields, uffHorAlign);
|
|
case s of
|
|
'Left':
|
|
HorAlignment := haLeft;
|
|
'Center':
|
|
HorAlignment := haCenter;
|
|
'Right':
|
|
HorAlignment := haRight;
|
|
else
|
|
Exclude(UsedFormattingFields, uffHorAlign);
|
|
end;
|
|
end;
|
|
|
|
// Vertical text
|
|
s := GetAttrValue(ANode, 'ss:Rotate');
|
|
if s = '90' then
|
|
with AFormat do begin
|
|
TextRotation := rt90DegreeCounterClockwiseRotation;
|
|
Include(UsedFormattingFields, uffTextRotation);
|
|
end
|
|
else if s = '-90' then
|
|
with AFormat do begin
|
|
TextRotation := rt90DegreeClockwiseRotation;
|
|
Include(UsedFormattingFields, uffTextRotation);
|
|
end;
|
|
s := GetAttrValue(ANode, 'ss:VerticalText');
|
|
if s <> '' then
|
|
with AFormat do begin
|
|
TextRotation := rtStacked;
|
|
Include(UsedFormattingFields, uffTextRotation);
|
|
end;
|
|
|
|
// Word wrap
|
|
s := GetAttrValue(ANode, 'ss:WrapText');
|
|
if s = '1' then
|
|
with AFormat do
|
|
Include(UsedFormattingFields, uffWordWrap);
|
|
|
|
// BiDi
|
|
s := GetAttrValue(ANode, 'ss:ReadingOrder');
|
|
if s <> '' then
|
|
with AFormat do begin
|
|
case s of
|
|
'RightToLeft': BiDiMode := bdRTL;
|
|
'LeftToRight': BiDiMode := bdLTR;
|
|
end;
|
|
Include(UsedFormattingFields, uffBiDi);
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Read a "Style/Borders/Border" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadBorder(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
// <Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="3" ss:Color="#ED7D31"/>
|
|
var
|
|
s, sw: String;
|
|
b: TsCellBorder;
|
|
begin
|
|
AFormat.UsedFormattingFields := AFormat.UsedFormattingFields + [uffBorder];
|
|
|
|
// Border position
|
|
s := GetAttrValue(ANode, 'ss:Position');
|
|
case s of
|
|
'Left':
|
|
b := cbWest;
|
|
'Right':
|
|
b := cbEast;
|
|
'Top':
|
|
b := cbNorth;
|
|
'Bottom':
|
|
b := cbSouth;
|
|
'DiagonalRight':
|
|
b := cbDiagUp;
|
|
'DiagonalLeft':
|
|
b := cbDiagDown;
|
|
end;
|
|
Include(AFormat.Border, b);
|
|
|
|
// Border color
|
|
s := GetAttrValue(ANode, 'ss:Color');
|
|
if s = '' then
|
|
AFormat.BorderStyles[b].Color := scBlack
|
|
else
|
|
AFormat.BorderStyles[b].Color := HTMLColorStrToColor(s);
|
|
|
|
// Line style
|
|
s := GetAttrValue(ANode, 'ss:LineStyle');
|
|
sw := GetAttrValue(ANode, 'ss:Weight');
|
|
case s of
|
|
'Continuous':
|
|
if sw = '1' then
|
|
AFormat.BorderStyles[b].LineStyle := lsThin
|
|
else if sw = '2' then
|
|
AFormat.BorderStyles[b].LineStyle := lsMedium
|
|
else if sw = '3' then
|
|
AFormat.BorderStyles[b].LineStyle := lsThick
|
|
else if sw = '' then
|
|
AFormat.BorderStyles[b].LineStyle := lsHair;
|
|
'Double':
|
|
AFormat.BorderStyles[b].LineStyle := lsDouble;
|
|
'Dot':
|
|
AFormat.BorderStyles[b].LineStyle := lsDotted;
|
|
'Dash':
|
|
if sw = '2' then
|
|
AFormat.BorderStyles[b].LineStyle := lsMediumDash
|
|
else
|
|
AFormat.BorderStyles[b].LineStyle := lsDashed;
|
|
'DashDot':
|
|
if sw = '2' then
|
|
AFormat.BorderStyles[b].LineStyle := lsMediumDashDot
|
|
else
|
|
AFormat.BorderStyles[b].LineStyle := lsDashDot;
|
|
'DashDotDot':
|
|
if sw = '2' then
|
|
AFormat.BorderStyles[b].LineStyle := lsMediumDashDotDot
|
|
else
|
|
AFormat.BorderStyles[b].LineStyle := lsDashDotDot;
|
|
'SlantDashDot':
|
|
AFormat.BorderStyles[b].LineStyle := lsSlantDashDot;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Styles/Style/Borders" nodes
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadBorders(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
nodeName: String;
|
|
begin
|
|
if ANode = nil then exit;
|
|
ANode := ANode.FirstChild;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Border' then
|
|
ReadBorder(ANode, AFormat);
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads a "Worksheet/Table/Row/Cell" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadCell(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet; ARow, ACol: Integer);
|
|
var
|
|
book: TsWorkbook;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
nodeName: string;
|
|
s, st, sv: String;
|
|
txt: String;
|
|
node: TDOMNode;
|
|
err: TsErrorValue;
|
|
cell: PCell;
|
|
fmt: TsCellFormat;
|
|
nfp: TsNumFormatParams;
|
|
idx: Integer;
|
|
mergedCols, mergedRows: Integer;
|
|
font: TsFont;
|
|
dt: TDateTime;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
nodeName := ANode.NodeName;
|
|
if nodeName <> 'Cell' then
|
|
raise Exception.Create('[ReadCell] "Cell" node expected.');
|
|
|
|
book := TsWorkbook(FWorkbook);
|
|
font := book.GetDefaultFont;
|
|
|
|
if FIsVirtualMode then
|
|
begin
|
|
if not Assigned(book.OnReadCellData) then
|
|
exit;
|
|
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
|
cell := @FVirtualCell;
|
|
end else
|
|
cell := sheet.AddCell(ARow, ACol);
|
|
|
|
s := GetAttrValue(ANode, 'ss:StyleID');
|
|
if s <> '' then
|
|
begin
|
|
idx := FCellFormatList.FindIndexOfName(s);
|
|
if idx <> -1 then begin
|
|
fmt := FCellFormatList.Items[idx]^;
|
|
cell^.FormatIndex := book.AddCellFormat(fmt);
|
|
font := book.GetFont(fmt.FontIndex);
|
|
end;
|
|
end else
|
|
begin
|
|
InitFormatRecord(fmt);
|
|
cell^.FormatIndex := 0;
|
|
end;
|
|
|
|
// Merged cells
|
|
s := GetAttrValue(ANode, 'ss:MergeAcross');
|
|
if not ((s <> '') and TryStrToInt(s, mergedCols)) then mergedCols := 0;
|
|
s := GetAttrValue(ANode, 'ss:MergeDown');
|
|
if not ((s <> '') and TryStrToint(s, mergedRows)) then mergedRows := 0;
|
|
if (mergedCols > 0) or (mergedRows > 0) then
|
|
sheet.MergeCells(ARow, ACol, ARow + mergedRows, ACol + mergedCols);
|
|
|
|
// Formula
|
|
s := GetAttrValue(ANode, 'ss:Formula');
|
|
if s <> '' then begin
|
|
try
|
|
sheet.WriteFormula(cell, s, false, true);
|
|
except
|
|
on E:EExprParser do begin
|
|
FWorkbook.AddErrorMsg(E.Message);
|
|
if (boAbortReadOnFormulaError in FWorkbook.Options) then raise;
|
|
end;
|
|
on E:ECalcEngine do begin
|
|
FWorkbook.AddErrorMsg(E.Message);
|
|
if (boAbortReadOnFormulaError in FWorkbook.Options) then raise;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Hyperlink
|
|
s := GetAttrValue(ANode, 'ss:HRef');
|
|
if s <> '' then begin
|
|
st := GetAttrValue(ANode, 'x:HRefScreenTip');
|
|
sheet.WriteHyperlink(cell, s, st);
|
|
end;
|
|
|
|
// Cell data and comment
|
|
node := ANode.FirstChild;
|
|
if node = nil then
|
|
sheet.WriteBlank(cell)
|
|
else begin
|
|
book.LockFormulas; // Protect formulas from being deleted by the WriteXXXX calls
|
|
try
|
|
while node <> nil do begin
|
|
nodeName := node.NodeName;
|
|
if (nodeName = 'Data') or (nodeName = 'ss:Data') then begin
|
|
sv := node.TextContent;
|
|
st := GetAttrValue(node, 'ss:Type');
|
|
case st of
|
|
'String':
|
|
sheet.WriteText(cell, sv);
|
|
'Number':
|
|
sheet.WriteNumber(cell, StrToFloat(sv, FPointSeparatorSettings));
|
|
'DateTime':
|
|
begin
|
|
dt := ExtractDateTime(sv);
|
|
if (cell^.FormatIndex > 0) then begin
|
|
nfp := TsWorkbook(FWorkbook).GetNumberFormat(fmt.NumberFormatIndex);
|
|
if not IsTimeIntervalFormat(nfp) then
|
|
dt := ConvertExcelDateTimeToDateTime(dt, FDateMode);
|
|
end;
|
|
sheet.WriteDateTime(cell, dt);
|
|
end;
|
|
'Boolean':
|
|
if sv = '1' then
|
|
sheet.WriteBoolValue(cell, true)
|
|
else if sv = '0' then
|
|
sheet.WriteBoolValue(cell, false);
|
|
'Error':
|
|
if TryStrToErrorValue(sv, err) then
|
|
sheet.WriteErrorValue(cell, err);
|
|
end;
|
|
if nodeName = 'ss:Data' then begin
|
|
txt := '';
|
|
RebuildChildNodes(node, txt);
|
|
HTMLToRichText(FWorkbook, font, txt, s, cell^.RichTextParams, 'html:');
|
|
end;
|
|
end
|
|
else
|
|
if (nodeName = 'Comment') then
|
|
ReadComment(node, AWorksheet, cell);
|
|
node := node.NextSibling;
|
|
end;
|
|
|
|
if FIsVirtualMode then
|
|
book.OnReadCellData(book, ARow, ACol, cell);
|
|
|
|
finally
|
|
book.UnlockFormulas;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Styles/Style/Protection" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadCellProtection(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
s: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
s := GetAttrValue(ANode, 'ss:Protected');
|
|
if s = '0' then
|
|
Exclude(AFormat.Protection, cpLockCell);
|
|
|
|
s := GetAttrValue(ANode, 'x:HideFormula');
|
|
if s = '1' then
|
|
Include(AFormat.Protection, cpHideFormulas);
|
|
|
|
if AFormat.Protection <> DEFAULT_CELL_PROTECTION then
|
|
Include(AFormat.UsedFormattingFields, uffProtection);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet/Table/Row/Cell/Comment" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadComment(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet; ACell: PCell);
|
|
var
|
|
txt: String;
|
|
begin
|
|
txt := ANode.TextContent;
|
|
TsWorksheet(AWorksheet).WriteComment(ACell, txt);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet/ConditionalFormatting" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadConditionalFormatting(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
book: TsWorkbook;
|
|
sheet: TsWorksheet;
|
|
childNode: TDOMNode;
|
|
nodeName: String;
|
|
s, nameStr, valueStr, tmpStr: String;
|
|
range: TsCellRange;
|
|
flags: TsRelFlags;
|
|
i, j: Integer;
|
|
c: TsCFCondition;
|
|
condition: Integer;
|
|
op1, op2: Variant;
|
|
fgColor, bgColor: TsColor;
|
|
fs, fill: TsFillStyle;
|
|
p: Integer;
|
|
L: TStrings;
|
|
sa: TStringArray;
|
|
fmt: TsCellFormat;
|
|
fmtIndex: Integer;
|
|
fntstyle: TsFontStyles;
|
|
fntColor: TsColor;
|
|
fnt: TsFont;
|
|
cb: TsCellBorder;
|
|
borders: TsCellBorders;
|
|
lineStyle: Integer;
|
|
lineColor: TsColor;
|
|
commonBorder: TsCellBorderStyle;
|
|
borderStyles: TsCellBorderStyles;
|
|
parser: TsSpreadsheetParser;
|
|
begin
|
|
sheet := TsWorksheet(AWorksheet);
|
|
book := TsWorkbook(FWorkbook);
|
|
|
|
// initialize parameters
|
|
condition := -1;
|
|
range := fpsUtils.Range(Cardinal(-1), Cardinal(-1), Cardinal(-1), Cardinal(-1));
|
|
VarClear(op1{%H-});
|
|
VarClear(op2{%H-});
|
|
bgColor := scNotDefined;
|
|
fgColor := scNotDefined;
|
|
fill := fsNoFill;
|
|
fntStyle := [];
|
|
fntColor := scNotDefined;
|
|
commonBorder := NO_CELL_BORDER;
|
|
borderStyles[cbNorth] := NO_CELL_BORDER;
|
|
borderStyles[cbSouth] := NO_CELL_BORDER;
|
|
borderStyles[cbEast] := NO_CELL_BORDER;
|
|
borderStyles[cbWest] := NO_CELL_BORDER;
|
|
borders := [];
|
|
|
|
nodeName := ANode.NodeName; // for debugging
|
|
|
|
// Read nodes
|
|
while ANode <> nil do
|
|
begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Range' then
|
|
begin
|
|
s := GetNodeValue(ANode);
|
|
if not ParseCellRangeString_R1C1(s, 0, 0,
|
|
range.Row1, range.Col1, range.Row2, range.Col2, flags) then
|
|
begin
|
|
book.AddErrorMsg('Conditional format range %s not readable', [s]);
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
if nodeName = 'Condition' then
|
|
begin
|
|
childNode := ANode.FirstChild;
|
|
while childNode <> nil do
|
|
begin
|
|
nodeName := childNode.NodeName;
|
|
if nodeName = 'Qualifier' then
|
|
begin
|
|
s := GetNodeValue(childNode);
|
|
if (s <> '') and (s[1] <> '@') then
|
|
begin
|
|
for c in TsCFCondition do
|
|
if s = CF_CONDITIONS[c] then
|
|
begin
|
|
condition := ord(c);
|
|
break;
|
|
end;
|
|
end;
|
|
end else
|
|
if nodeName = 'Value1' then
|
|
begin
|
|
s := GetNodeValue(childNode);
|
|
if s <> '' then
|
|
op1 := s;
|
|
end else
|
|
if nodeName = 'Value2' then
|
|
begin
|
|
s := GetNodeValue(childNode);
|
|
if s <> '' then
|
|
op2 := s;
|
|
end else
|
|
if nodeName = 'Format' then
|
|
begin
|
|
s := GetAttrValue(childNode, 'Style');
|
|
L := TStringList.Create;
|
|
try
|
|
L.Delimiter := ';';
|
|
L.NameValueSeparator := ':';
|
|
L.StrictDelimiter := true;
|
|
L.DelimitedText := s;
|
|
for i := 0 to L.Count-1 do
|
|
begin
|
|
nameStr := Trim(L.Names[i]);
|
|
valueStr := Trim(L.ValueFromIndex[i]);
|
|
case nameStr of
|
|
'background':
|
|
bgColor := HTMLColorStrToColor(valueStr);
|
|
'mso-pattern':
|
|
for fs in TsFillStyle do
|
|
begin
|
|
p := pos(CF_FILL_NAMES[fs], valueStr);
|
|
if p > 0 then begin
|
|
fill := fs;
|
|
Delete(valueStr, p, Length(CF_FILL_NAMES[fs]));
|
|
fgColor := HTMLColorStrToColor(Trim(valueStr));
|
|
break;
|
|
end;
|
|
end;
|
|
'font-style':
|
|
if valueStr = 'italic' then
|
|
fntStyle := fntStyle + [fssItalic];
|
|
'font-weight':
|
|
if StrToInt(valueStr) > 500 then
|
|
fntStyle := fntStyle + [fssBold];
|
|
'text-line-through':
|
|
fntStyle := fntStyle + [fssStrikeOut];
|
|
'color':
|
|
fntColor := HTMLColorStrToColor(valueStr);
|
|
'border', 'border-top', 'border-bottom', 'border-left', 'border-right':
|
|
begin
|
|
if nameStr = 'border' then
|
|
borders := ALL_BORDERS
|
|
else
|
|
begin
|
|
if not TryStrToCFCellBorder(nameStr, cb) then
|
|
Continue;
|
|
if valueStr = 'none' then
|
|
Continue;
|
|
end;
|
|
sa := valueStr.Split(' ');
|
|
lineColor := scNotDefined;
|
|
lineStyle := -1;
|
|
for j := 0 to High(sa) do begin
|
|
tmpStr := Trim(sa[j]);
|
|
// Line width not supported
|
|
if pos('pt', tmpStr) > 0 then
|
|
Continue;
|
|
// Extract line style
|
|
if (linestyle = -1) and TryStrToCFLineStyle(tmpStr, TsLineStyle(linestyle)) then
|
|
Continue;
|
|
// Extract line color
|
|
if (lineColor = scNotDefined) then
|
|
lineColor := HTMLColorStrToColor(tmpStr);
|
|
end;
|
|
if nameStr = 'border' then
|
|
begin
|
|
if linestyle = -1 then
|
|
commonBorder.LineStyle := lsThin
|
|
else
|
|
commonBorder.LineStyle := TsLineStyle(linestyle);
|
|
commonBorder.Color := lineColor;
|
|
end else
|
|
begin
|
|
Include(borders, cb);
|
|
if lineStyle = -1 then
|
|
borderStyles[cb].LineStyle := lsThin
|
|
else
|
|
borderStyles[cb].LineStyle := TsLineStyle(linestyle);
|
|
borderStyles[cb].Color := lineColor;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
L.Free;
|
|
end;
|
|
end;
|
|
childNode := childNode.NextSibling;
|
|
end;
|
|
|
|
if (condition = -1) and (op1 <> '') then
|
|
begin
|
|
AnalyzeCFExpression(op1, TsCFCondition(condition), s);
|
|
if s = '' then
|
|
VarClear(op1)
|
|
else
|
|
if TsCFCondition(condition) = cfcExpression then
|
|
begin
|
|
parser := TsSpreadsheetParser.Create(AWorksheet);
|
|
try
|
|
try
|
|
parser.R1C1Expression[nil] := s; // Parse in Excel-R1C1 dialect
|
|
op1 := parser.Expression[fdExcelA1]; // Convert to Excel-A1 dialect
|
|
except
|
|
VarClear(op1);
|
|
end;
|
|
finally
|
|
parser.Free;
|
|
end;
|
|
end else
|
|
op1 := s;
|
|
end;
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
|
|
if (range.Row1 = Cardinal(-1)) or (range.Col1 = Cardinal(-1)) or
|
|
(range.Row2 = Cardinal(-1)) or (Range.Col2 = Cardinal(-1)) then
|
|
begin
|
|
book.AddErrorMsg('Missing cell range for conditional formatting.');
|
|
exit;
|
|
end;
|
|
|
|
if condition = -1 then
|
|
begin
|
|
book.AddErrorMsg('No condition given in conditional format.');
|
|
exit;
|
|
end;
|
|
|
|
// Prepare format record used by the conditional format
|
|
InitFormatRecord(fmt);
|
|
// ... background
|
|
if (bgColor <> scNotDefined) or (fgColor <> scNotDefined) or (fill <> fsNoFill) then
|
|
begin
|
|
if fgColor = scNotDefined then
|
|
fmt.SetBackgroundColor(bgColor)
|
|
else
|
|
fmt.SetBackground(fill, fgColor, bgColor);
|
|
end;
|
|
// ... font
|
|
if (fntStyle <> []) or (fntColor <> scNotDefined) then
|
|
begin
|
|
fnt := book.CloneFont(fmt.FontIndex);
|
|
if fntStyle <> [] then
|
|
fnt.Style := fntStyle;
|
|
if fntColor <> scNotDefined then
|
|
fnt.Color := fntColor;
|
|
fmt.SetFont(book.AddFont(fnt));
|
|
end;
|
|
// .. borders
|
|
if commonBorder.Color <> scNotDefined then
|
|
fmt.SetBorders(ALL_BORDERS, commonBorder.Color, commonBorder.LineStyle)
|
|
else
|
|
for cb in borders do
|
|
fmt.SetBorders([cb], borderStyles[cb].Color, borderStyles[cb].LineStyle);
|
|
|
|
// Add format record to format list
|
|
fmtIndex := book.AddCellFormat(fmt);
|
|
|
|
// Attach as conditional format to the given cell range of the worksheet
|
|
sheet.WriteConditionalCellFormat(range, TsCFCondition(condition), op1, op2, fmtIndex);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Read the custom meta data fields
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadCustomDocumentProperties(ANode: TDOMNode);
|
|
var
|
|
book: TsWorkbook;
|
|
value: String;
|
|
nodeName: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
book := TsWorkbook(FWorkbook);
|
|
ANode := ANode.FirstChild;
|
|
while ANode <> nil do
|
|
begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName <> '#text' then
|
|
begin
|
|
value := GetNodeValue(ANode);
|
|
book.MetaData.AddCustom(nodeName, value);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the meta data etc.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadDocumentProperties(ANode: TDOMNode);
|
|
var
|
|
book: TsWorkbook;
|
|
nodeName: String;
|
|
s: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
book := TsWorkbook(FWorkbook);
|
|
ANode := ANode.FirstChild;
|
|
while ANode <> nil do
|
|
begin
|
|
nodeName := ANode.NodeName;
|
|
s := GetNodeValue(ANode);
|
|
case nodeName of
|
|
'Title':
|
|
book.MetaData.Title := s;
|
|
'Subject':
|
|
book.MetaData.Subject := s;
|
|
'Author':
|
|
book.MetaData.CreatedBy := s;
|
|
'LastAuthor':
|
|
book.MetaData.LastModifiedBy := s;
|
|
'Created':
|
|
if s <> '' then
|
|
book.MetaData.DateCreated := ISO8601StrToDateTime(s);
|
|
'LastSaved':
|
|
if s <> '' then
|
|
book.MetaData.DateLastModified := ISO8601StrToDateTime(s);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
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 else
|
|
if nodeName = 'Date1904' then
|
|
FDateMode := dm1904;
|
|
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Styles/Style/Font" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLreader.ReadFont(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
book: TsWorkbook;
|
|
fname: String;
|
|
fsize: Single;
|
|
fcolor: TsColor;
|
|
fstyle: TsFontStyles;
|
|
s: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
book := TsWorkbook(FWorkbook);
|
|
|
|
fname := GetAttrValue(ANode, 'ss:FontName');
|
|
if fname = '' then
|
|
fname := book.GetDefaultFont.FontName;
|
|
|
|
s := GetAttrValue(ANode, 'ss:Size');
|
|
if (s = '') or not TryStrToFloat(s, fsize, FPointSeparatorSettings) then
|
|
fsize := book.GetDefaultFont.Size;
|
|
|
|
s := GetAttrValue(ANode, 'ss:Color');
|
|
if s <> '' then
|
|
fcolor := HTMLColorStrToColor(s)
|
|
else
|
|
fcolor := book.GetDefaultFont.Color;
|
|
|
|
fstyle := [];
|
|
s := GetAttrValue(ANode, 'ss:Bold');
|
|
if s = '1' then
|
|
Include(fstyle, fssBold);
|
|
s := GetAttrValue(ANode, 'ss:Italic');
|
|
if s = '1' then
|
|
Include(fstyle, fssItalic);
|
|
s := GetAttrValue(ANode, 'ss:Underline');
|
|
if s <> '' then
|
|
Include(fstyle, fssUnderline);
|
|
s := GetAttrValue(ANode, 'ss:StrikeThrough');
|
|
if s = '1' then
|
|
Include(fstyle, fssStrikeout);
|
|
|
|
AFormat.FontIndex := book.AddFont(fname, fsize, fstyle, fcolor);
|
|
Include(AFormat.UsedFormattingFields, uffFont);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Styles/Style/Interior" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadInterior(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
s, sfg, sbg: String;
|
|
fs: TsFillStyle;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
// Pattern
|
|
s := GetAttrValue(ANode, 'ss:Pattern');
|
|
if s = '' then
|
|
exit;
|
|
|
|
for fs in TsFillStyle do
|
|
if FILL_NAMES[fs] = s then begin
|
|
AFormat.Background.Style := fs;
|
|
break;
|
|
end;
|
|
|
|
// Foreground color (pattern color)
|
|
sfg := GetAttrValue(ANode, 'ss:PatternColor');
|
|
if sfg = '' then
|
|
AFormat.Background.FgColor := scBlack
|
|
else
|
|
AFormat.Background.FgColor := HTMLColorStrToColor(sfg);
|
|
|
|
// Background color
|
|
sbg := GetAttrValue(ANode, 'ss:Color');
|
|
if sbg = '' then
|
|
AFormat.Background.BgColor := scWhite
|
|
else
|
|
AFormat.Background.BgColor := HTMLColorStrToColor(sbg);
|
|
|
|
// Fix solid fill colors: make foreground and background color the same
|
|
if AFormat.Background.Style = fsSolidFill then begin
|
|
if (sfg <> '') then
|
|
AFormat.Background.BgColor := AFormat.Background.FgColor // Forground priority
|
|
else if (sfg = '') and (sbg <> '') then
|
|
AFormat.Background.FgColor := AFormat.Background.BgColor;
|
|
end;
|
|
|
|
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
|
|
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
|
|
s := GetAttrValue(ANode, 'ss:Name');
|
|
if s = 'Print_Area' then begin
|
|
// <NamedRange ss:Name="Print_Area" ss:RefersTo="=Tabelle2!R2C2:R5C7"/>
|
|
s := GetAttrValue(ANode, 'ss:RefersTo');
|
|
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
|
|
// <NamedRange ss:Name="Print_Titles" ss:RefersTo="=Tabelle2!C1,Tabelle2!R1:R2"/>
|
|
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
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadNumberFormat(ANode: TDOMNode;
|
|
var AFormat: TsCellFormat);
|
|
var
|
|
s: String;
|
|
nf: TsNumberFormat = nfGeneral;
|
|
nfs: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
s := GetAttrValue(ANode, 'ss:Format');
|
|
case s of
|
|
'General': Exit;
|
|
'Standard':
|
|
begin
|
|
nf := nfFixedTh;
|
|
nfs := BuildNumberFormatString(nf, FWorkbook.FormatSettings, 2);
|
|
end;
|
|
'Fixed':
|
|
begin
|
|
nf := nfFixed;
|
|
nfs := BuildNumberFormatString(nf, FWorkbook.FormatSettings, 2);
|
|
end;
|
|
'Percent':
|
|
begin
|
|
nf := nfPercentage;
|
|
nfs := BuildNumberFormatString(nf, FWorkbook.FormatSettings, 2);
|
|
end;
|
|
'Scientific':
|
|
begin
|
|
nf := nfExp;
|
|
nfs := BuildNumberFormatString(nf, FWorkbook.FormatSettings);
|
|
end;
|
|
'Short Date':
|
|
begin
|
|
nf := nfShortDate;
|
|
nfs := BuildDateTimeFormatString(nf, FWorkbook.FormatSettings);
|
|
end;
|
|
'Short Time':
|
|
begin
|
|
nf := nfShortTime;
|
|
nfs := BuildDateTimeFormatString(nf, FWorkbook.FormatSettings);
|
|
end;
|
|
else
|
|
nfs := s;
|
|
end;
|
|
if nfs = '' then
|
|
exit;
|
|
|
|
AFormat.NumberFormatIndex := TsWorkbook(FWorkbook).AddNumberFormat(nfs);
|
|
AFormat.NumberFormatStr := nfs;
|
|
AFormat.NumberFormat := nf;
|
|
Include(AFormat.UsedFormattingFields, uffNumberFormat);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads a "Worksheet / PageBreaks / RowBreaks / RowBreak" node
|
|
or a "Worksheet / PageBreaks / ColBreaks / ColBreak" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadPageBreak(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
nodeName: String;
|
|
s: String;
|
|
n: Integer;
|
|
begin
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Row' then begin
|
|
s := ANode.TextContent;
|
|
if (s <> '') and TryStrToInt(s, n) then
|
|
sheet.AddPageBreakToRow(n);
|
|
end else
|
|
if nodeName = 'Column' then begin
|
|
s := ANode.TextContent;
|
|
if (s <> '') and TryStrToInt(s, n) then
|
|
sheet.AddPageBreakToCol(n);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Wrksheet / PageBreaks" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadPageBreaks(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
nodeName: String;
|
|
node: TDOMNode;
|
|
begin
|
|
while ANode <> nil do
|
|
begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'RowBreaks' then begin
|
|
node := ANode.FirstChild;
|
|
while node <> nil do begin
|
|
nodeName := node.NodeName;
|
|
if nodeName = 'RowBreak' then
|
|
ReadPageBreak(node.FirstChild, AWorksheet);
|
|
node := node.NextSibling;
|
|
end;
|
|
end else
|
|
if nodeName = 'ColBreaks' then begin
|
|
node := ANode.FirstChild;
|
|
while node <> nil do begin
|
|
nodeName := node.NodeName;
|
|
if nodeName = 'ColBreak' then
|
|
ReadPageBreak(node.FirstChild, AWorksheet);
|
|
node := node.NextSibling;
|
|
end;
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "WorksheetOptions/PageSetup" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadPageSetup(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 = 'Layout' then begin
|
|
s := GetAttrValue(ANode, 'x:Orientation');
|
|
if s = 'Landscape' then
|
|
sheet.PageLayout.Orientation := spoLandscape;
|
|
s := GetAttrValue(ANode, 'x:CenterHorizontal');
|
|
if s = '1' then
|
|
sheet.PageLayout.Options := sheet.PageLayout.Options + [poHorCentered];
|
|
s := GetAttrValue(ANode, 'x:CenterVertical');
|
|
if s = '1' then
|
|
sheet.PageLayout.Options := sheet.PageLayout.Options + [poVertCentered];
|
|
s := GetAttrValue(ANode, 'x:StartPageNumber');
|
|
if (s <> '') and TryStrToInt(s, n) then
|
|
sheet.PageLayout.StartPageNumber := n;
|
|
end
|
|
else if nodeName = 'Header' then begin
|
|
s := GetAttrValue(ANode, 'x:Margin');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.HeaderMargin := InToMM(x);
|
|
s := GetAttrValue(ANode, 'x:Data');
|
|
sheet.PageLayout.Headers[0] := s;
|
|
sheet.PageLayout.Headers[1] := s;
|
|
sheet.PageLayout.Headers[2] := s;
|
|
end
|
|
else if nodeName = 'Footer' then begin
|
|
s := GetAttrValue(ANode, 'x:Margin');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.FooterMargin := InToMM(x);
|
|
s := GetAttrValue(ANode, 'x:Data');
|
|
sheet.PageLayout.Footers[0] := s;
|
|
sheet.PageLayout.Footers[1] := s;
|
|
sheet.PageLayout.Footers[2] := s;
|
|
end
|
|
else if nodeName = 'PageMargins' then begin
|
|
s := GetAttrValue(ANode, 'x:Bottom');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.BottomMargin := InToMM(x);
|
|
s := GetAttrValue(ANode, 'x:Top');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.TopMargin := InToMM(x);
|
|
s := GetAttrValue(ANode, 'x:Left');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.LeftMargin := InToMM(x);
|
|
s := GetAttrValue(ANode, 'x:Right');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.PageLayout.RightMargin := InToMM(x);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
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;
|
|
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
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadRow(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet; ARow: Integer);
|
|
var
|
|
nodeName: String;
|
|
s: String;
|
|
c: Integer;
|
|
begin
|
|
c := 0;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Cell' then begin
|
|
s := GetAttrValue(ANode, 'ss:Index');
|
|
if s <> '' then c := StrToInt(s) - 1;
|
|
ReadCell(ANode, AWorksheet, ARow, c);
|
|
inc(c);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads a "Styles/Style" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadStyle(ANode: TDOMNode);
|
|
var
|
|
nodeName: String;
|
|
fmt: TsCellFormat;
|
|
s: String;
|
|
idx: Integer;
|
|
childNode: TDOMNode;
|
|
begin
|
|
// Respect ancestor of current style
|
|
s := GetAttrValue(ANode, 'ss:Parent');
|
|
if s <> '' then begin
|
|
idx := FCellFormatList.FindIndexOfName(s);
|
|
if idx > -1 then
|
|
fmt := FCellFormatList.Items[idx]^;
|
|
end else
|
|
InitFormatRecord(fmt);
|
|
|
|
// ID of current style. We store it in the "Name" field of the TsCellFormat
|
|
// because it is a string while ID is an Integer (mostly "s<number>", but also
|
|
// "Default").
|
|
fmt.Name := GetAttrValue(ANode, 'ss:ID');
|
|
|
|
if fmt.Name = 's125' then
|
|
idx := 0;
|
|
|
|
// Style elements
|
|
childNode := ANode.FirstChild;
|
|
while childNode <> nil do begin
|
|
nodeName := childNode.NodeName;
|
|
if nodeName = 'Alignment' then
|
|
ReadAlignment(childNode, fmt)
|
|
else if nodeName = 'Borders' then
|
|
ReadBorders(childNode, fmt)
|
|
else if nodeName = 'Interior' then
|
|
ReadInterior(childNode, fmt)
|
|
else if nodeName = 'Font' then
|
|
ReadFont(childNode, fmt)
|
|
else if nodeName = 'NumberFormat' then
|
|
ReadNumberFormat(childnode, fmt)
|
|
else if nodeName = 'Protection' then
|
|
ReadCellProtection(childNode, fmt);
|
|
childNode := childNode.NextSibling;
|
|
end;
|
|
|
|
FCellFormatList.Add(fmt);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Styles" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadStyles(ANode: TDOMNode);
|
|
var
|
|
nodeName: String;
|
|
styleNode: TDOMNode;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
styleNode := ANode.FirstChild;
|
|
while styleNode <> nil do begin
|
|
nodeName := styleNode.NodeName;
|
|
if nodeName = 'Style' then
|
|
ReadStyle(styleNode);
|
|
styleNode := styleNode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet/Table" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadTable(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
nodeName: String;
|
|
s: String;
|
|
r, c: Integer;
|
|
x: Double;
|
|
idx: Integer;
|
|
fmt: TsCellFormat;
|
|
rht: TsRowHeightType;
|
|
begin
|
|
r := 0;
|
|
c := 0;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Column' then begin
|
|
// Default column width
|
|
s := GetAttrValue(ANode, 'ss:DefaultColumnWidth');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.WriteDefaultColWidth(x, suPoints);
|
|
|
|
// Column index
|
|
s := GetAttrValue(ANode, 'ss:Index');
|
|
if (s <> '') and TryStrToInt(s, c) then
|
|
dec(c);
|
|
|
|
// Column width, in Points
|
|
s := GetAttrValue(ANode, 'ss:Width');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.WriteColWidth(c, x, suPoints);
|
|
|
|
// Column format
|
|
s := GetAttrValue(ANode, 'ss:StyleID');
|
|
if s <> '' then begin
|
|
idx := FCellFormatList.FindIndexOfName(s);
|
|
if idx <> -1 then begin
|
|
fmt := FCellFormatList.Items[idx]^;
|
|
idx := TsWorkbook(FWorkbook).AddCellFormat(fmt);
|
|
sheet.WriteColFormatIndex(c, idx);
|
|
end;
|
|
end;
|
|
|
|
// Hidden
|
|
s := GetAttrValue(ANode, 'ss:Hidden');
|
|
if s = '1' then
|
|
sheet.HideCol(c);
|
|
|
|
inc(c);
|
|
end
|
|
else
|
|
if nodeName = 'Row' then begin
|
|
// Default row height
|
|
s := GetAttrValue(ANode, 'ss:DefaultRowHeight');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.WriteDefaultRowHeight(x, suPoints);
|
|
|
|
// Index
|
|
s := GetAttrValue(ANode, 'ss:Index');
|
|
if s <> '' then r := StrToInt(s) - 1;
|
|
|
|
// AutoFitHeight
|
|
s := GetAttrValue(ANode, 'ss:AutoFitHeight');
|
|
if s = '1' then
|
|
rht := rhtAuto
|
|
else
|
|
rht := rhtCustom;
|
|
|
|
// Height
|
|
s := GetAttrValue(ANode, 'ss:Height');
|
|
if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then
|
|
sheet.WriteRowHeight(r, x, suPoints, rht);
|
|
|
|
// Hidden
|
|
s := GetAttrValue(ANode, 'ss:Hidden');
|
|
if (s = '1') then
|
|
sheet.HideRow(r);
|
|
|
|
// Row format
|
|
s := GetAttrValue(ANode, 'ss:StyleID');
|
|
if s <> '' then begin
|
|
idx := FCellFormatList.FindIndexOfName(s);
|
|
if idx <> -1 then begin
|
|
fmt := FCellFormatList.Items[idx]^;
|
|
idx := TsWorkbook(FWorkbook).AddCellFormat(fmt);
|
|
sheet.WriteRowFormatIndex(r, idx);
|
|
end;
|
|
end;
|
|
|
|
// Cells in row
|
|
ReadRow(ANode.FirstChild, AWorksheet, r);
|
|
|
|
inc(r);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet" node
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadWorksheet(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
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)
|
|
else if nodeName = 'Names' then
|
|
ReadNames(ANode.FirstChild, AWorksheet)
|
|
else if nodeName = 'PageBreaks' then
|
|
ReadPageBreaks(ANode.FirstChild, AWorksheet)
|
|
else if nodeName = 'ConditionalFormatting' then
|
|
ReadConditionalFormatting(ANode.FirstChild, AWorksheet);
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet/WorksheetOptions" nodes
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadWorksheetOptions(ANode: TDOMNode;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
node, childnode: TDOMNode;
|
|
nodeName: String;
|
|
s: String;
|
|
x: Double;
|
|
n: Integer;
|
|
hasFitToPage: Boolean = false;
|
|
c, r: Cardinal;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'PageSetup' then
|
|
ReadPageSetup(ANode.FirstChild, AWorksheet)
|
|
else
|
|
if nodeName = 'FitToPage' then begin
|
|
hasFitToPage := true;
|
|
sheet.PageLayout.Options := sheet.PageLayout.Options + [poFitPages];
|
|
end else
|
|
if nodeName = 'Print' then begin
|
|
node := ANode.FirstChild;
|
|
ReadPrint(ANode.FirstChild, AWorksheet);
|
|
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;
|
|
|
|
if hasFitToPage then begin
|
|
// The ScalingFactor is always written to the xml file. This makes TsPageLayout
|
|
// automatically remove the poFitPages option which is restored here.
|
|
if (sheet.PageLayout.ScalingFactor <> 100) then begin
|
|
sheet.PageLayout.ScalingFactor := 100;
|
|
sheet.Pagelayout.Options := sheet.PageLayout.Options + [poFitPages];
|
|
end;
|
|
// When FitToPages is active, but FitWidthToPages and/or FitHeightToPages
|
|
// are not specified, they should be set to 1
|
|
if sheet.PageLayout.FitWidthToPages = 0 then
|
|
sheet.PageLayout.FitWidthToPages := 1;
|
|
if sheet.PageLayout.FitHeightToPages = 0 then
|
|
sheet.PageLayout.FitHeightToPages := 1;
|
|
end;
|
|
end;
|
|
|
|
(*
|
|
|
|
function TsSpreadExcelXMLWriter.GetLayoutStr(AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
Result := '';
|
|
if sheet.PageLayout.Orientation = spoLandscape then
|
|
Result := Result + ' x:Orientation="Landscape"';
|
|
if (poHorCentered in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:CenterHorizontal="1"';
|
|
if (poVertCentered in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:CenterVertical="1"';
|
|
if (poUseStartPageNumber in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:StartPageNumber="' + IntToStr(sheet.PageLayout.StartPageNumber) + '"';
|
|
Result := '<Layout' + Result + '/>';
|
|
end;
|
|
*)
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the "Worksheet" nodes
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadWorksheets(ANode: TDOMNode);
|
|
var
|
|
node: TDOMNode;
|
|
nodeName: String;
|
|
s: String;
|
|
begin
|
|
node := ANode;
|
|
// first iterate through all worksheets, get the name and add them to the
|
|
// workbook. This is because 3D formulas may refer to sheets not yet loaded.
|
|
while node <> nil do begin
|
|
nodeName := node.NodeName;
|
|
if nodeName = 'Worksheet' then begin
|
|
s := GetAttrValue(node, 'ss:Name');
|
|
if s <> '' then // the case of '' should not happen...
|
|
FWorksheet := TsWorkbook(FWorkbook).AddWorksheet(s);
|
|
end;
|
|
node := node.NextSibling;
|
|
end;
|
|
|
|
// Now iterate through the worksheets again and read their contents
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'Worksheet' then begin
|
|
s := GetAttrValue(ANode, 'ss:Name');
|
|
FWorksheet := TsWorkbook(FWorkbook).GetWorksheetByName(s);
|
|
ReadWorksheet(ANode, FWorksheet);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Reads the workbook from the specified stream
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLReader.ReadFromStream(AStream: TStream;
|
|
APassword: String = ''; AParams: TsStreamParams = []);
|
|
var
|
|
doc: TXMLDocument;
|
|
begin
|
|
Unused(APassword, AParams);
|
|
|
|
try
|
|
ReadXMLStream(doc, AStream);
|
|
|
|
// Read meta data
|
|
ReadDocumentProperties(doc.DocumentElement.FindNode('DocumentProperties'));
|
|
ReadCustomDocumentProperties(doc.DocumentElement.FindNode('CustomDocumentProperties'));
|
|
|
|
// 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
|
|
|
|
Defines the date mode and the limitations of the file format.
|
|
Initializes the format settings to be used when writing to xml.
|
|
-------------------------------------------------------------------------------}
|
|
constructor TsSpreadExcelXMLWriter.Create(AWorkbook: TsBasicWorkbook);
|
|
begin
|
|
inherited Create(AWorkbook);
|
|
|
|
// Initial base date in case it won't be set otherwise.
|
|
// Use 1900 to get a bit more range between 1900..1904.
|
|
FDateMode := ExcelXMLSettings.DateMode;
|
|
|
|
// Special version of FormatSettings using a point decimal separator for sure.
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
|
|
|
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
|
|
FLimitations.MaxColCount := 256;
|
|
FLimitations.MaxRowCount := 65536;
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetCommentStr(ACell: PCell): String;
|
|
var
|
|
comment: PsComment;
|
|
begin
|
|
Result := '';
|
|
comment := (FWorksheet as TsWorksheet).FindComment(ACell);
|
|
if Assigned(comment) then
|
|
Result := INDENT1 +
|
|
'<Comment><Data>' +
|
|
UTF8TextToXMLText(comment^.Text) +
|
|
'</Data></Comment>' +
|
|
LF + CELL_INDENT;
|
|
// If there will be some rich-text-like formatting in the future, use
|
|
// Result := '<Comment><ss:Data xmlns="http://www.w3.org/TR/REC-html40">'+comment^.Text+'</ss:Data></Comment>':
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetFormulaStr(ACell: PCell): String;
|
|
begin
|
|
if HasFormula(ACell) then
|
|
begin
|
|
Result := UTF8TextToXMLText((FWorksheet as TsWorksheet).ConvertFormulaDialect(ACell, fdExcelR1C1));
|
|
Result := ' ss:Formula="=' + Result + '"';
|
|
end else
|
|
Result := '';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetFrozenPanesStr(AWorksheet: TsBasicWorksheet;
|
|
AIndent: String): String;
|
|
var
|
|
activePane: Integer;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
if (soHasFrozenPanes in sheet.Options) then
|
|
begin
|
|
Result := AIndent +
|
|
'<FreezePanes/>' + LF + AIndent +
|
|
'<FrozenNoSplit/>' + LF;
|
|
|
|
if sheet.LeftPaneWidth > 0 then
|
|
Result := Result + AIndent +
|
|
'<SplitVertical>1</SplitVertical>' + LF + AIndent +
|
|
'<LeftColumnRightPane>' + IntToStr(sheet.LeftPaneWidth) + '</LeftColumnRightPane>' + LF;
|
|
|
|
if sheet.TopPaneHeight > 0 then
|
|
Result := Result + AIndent +
|
|
'<SplitHorizontal>1</SplitHorizontal>' + LF + AIndent +
|
|
'<TopRowBottomPane>' + IntToStr(sheet.TopPaneHeight) + '</TopRowBottomPane>' + LF;
|
|
|
|
if (sheet.LeftPaneWidth = 0) and (sheet.TopPaneHeight = 0) then
|
|
activePane := 3
|
|
else
|
|
if (sheet.LeftPaneWidth = 0) then
|
|
activePane := 2
|
|
else
|
|
if (sheet.TopPaneHeight = 0) then
|
|
activePane := 1
|
|
else
|
|
activePane := 0;
|
|
Result := Result + AIndent +
|
|
'<ActivePane>' + IntToStr(activePane) + '</ActivePane>' + LF;
|
|
end else
|
|
Result := '';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetHyperlinkStr(ACell: PCell): String;
|
|
var
|
|
hyperlink: PsHyperlink;
|
|
begin
|
|
hyperlink := (FWorksheet as TsWorksheet).FindHyperlink(ACell);
|
|
if Assigned(hyperlink) then begin
|
|
Result := ' ss:HRef="' + hyperlink^.Target + '"';
|
|
if hyperlink^.ToolTip <> '' then
|
|
Result := Result + ' x:HRefScreenTip="' + UTF8TextToXMLText(hyperlink^.ToolTip) + '"';
|
|
end else
|
|
Result := '';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetIndexStr(AIndex, APrevIndex: Cardinal): String;
|
|
begin
|
|
if (APrevIndex = Cardinal(-1)) and (AIndex = 0) then
|
|
Result := ''
|
|
else
|
|
if (APrevIndex <> Cardinal(-1)) and (AIndex = APrevIndex + 1) then
|
|
Result := ''
|
|
else
|
|
Result := Format(' ss:Index="%d"', [AIndex + 1]);
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetLayoutStr(AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
Result := '';
|
|
if sheet.PageLayout.Orientation = spoLandscape then
|
|
Result := Result + ' x:Orientation="Landscape"';
|
|
if (poHorCentered in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:CenterHorizontal="1"';
|
|
if (poVertCentered in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:CenterVertical="1"';
|
|
if (poUseStartPageNumber in sheet.PageLayout.Options) then
|
|
Result := Result + ' x:StartPageNumber="' + IntToStr(sheet.PageLayout.StartPageNumber) + '"';
|
|
Result := '<Layout' + Result + '/>';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetMergeStr(ACell: PCell): String;
|
|
var
|
|
r1, c1, r2, c2: Cardinal;
|
|
begin
|
|
Result := '';
|
|
if (FWorksheet as TsWorksheet).IsMerged(ACell) then begin
|
|
(FWorksheet as TsWorksheet).FindMergedRange(ACell, r1, c1, r2, c2);
|
|
if c2 > c1 then
|
|
Result := Result + Format(' ss:MergeAcross="%d"', [c2-c1]);
|
|
if r2 > r1 then
|
|
Result := Result + Format(' ss:MergeDown="%d"', [r2-r1]);
|
|
end;
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetPageFooterStr(
|
|
AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
Result := Format('x:Margin="%g"', [mmToIn(sheet.PageLayout.FooterMargin)], FPointSeparatorSettings);
|
|
if (sheet.PageLayout.Footers[HEADER_FOOTER_INDEX_ALL] <> '') then
|
|
Result := Result + ' x:Data="' + UTF8TextToXMLText(sheet.PageLayout.Footers[HEADER_FOOTER_INDEX_ALL], true) + '"';
|
|
Result := '<Footer ' + result + '/>';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetPageHeaderStr(
|
|
AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
Result := Format('x:Margin="%g"', [mmToIn(sheet.PageLayout.HeaderMargin)], FPointSeparatorSettings);
|
|
if (sheet.PageLayout.Headers[HEADER_FOOTER_INDEX_ALL] <> '') then
|
|
Result := Result + ' x:Data="' + UTF8TextToXMLText(sheet.PageLayout.Headers[HEADER_FOOTER_INDEX_ALL], true) + '"';
|
|
Result := '<Header ' + Result + '/>';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetPageMarginStr(
|
|
AWorksheet: TsBasicWorksheet): String;
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
Result := Format('x:Bottom="%g" x:Left="%g" x:Right="%g" x:Top="%g"', [
|
|
mmToIn(sheet.PageLayout.BottomMargin),
|
|
mmToIn(sheet.PageLayout.LeftMargin),
|
|
mmToIn(sheet.PageLayout.RightMargin),
|
|
mmToIn(sheet.PageLayout.TopMargin)
|
|
], FPointSeparatorSettings);
|
|
Result := '<PageMargins ' + 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 + '<Scale>' + IntToStr(sheet.PageLayout.ScalingFactor) + '</Scale>' + LF
|
|
else
|
|
scaleStr := '';
|
|
|
|
Result :=
|
|
INDENT4 + '<ValidPrinterInfo/>' + LF +
|
|
INDENT4 + '<PaperSizeIndex>' + IntToStr(pgSizeIdx) + '</PaperSizeIndex>' + LF +
|
|
scaleStr +
|
|
INDENT4 + '<VerticalResolution>0</VerticalResolution>';
|
|
|
|
if sheet.PageLayout.FitHeightToPages > 1 then
|
|
Result := Result + LF + INDENT4 +
|
|
'<FitHeight>' + IntToStr(sheet.PageLayout.FitHeightToPages) + '</FitHeight>';
|
|
|
|
if sheet.PageLayout.FitWidthToPages > 1 then
|
|
Result := result + LF + INDENT4 +
|
|
'<FitWidth>' + IntToStr(sheet.PageLayout.FitWidthToPages) + '</FitWidth>';
|
|
end;
|
|
|
|
function TsSpreadExcelXMLWriter.GetStyleStr(AFormatIndex: Integer): String;
|
|
begin
|
|
Result := '';
|
|
if AFormatIndex > 0 then
|
|
Result := Format(' ss:StyleID="s%d"', [AFormatIndex + FMT_OFFSET]);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteBlank(AStream: TStream;
|
|
const ARow, ACol: Cardinal; ACell: PCell);
|
|
begin
|
|
Unused(ARow, ACol);
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s>' + // colIndex, style, hyperlink, merge
|
|
'%s' + // Comment <Comment>...</Comment>
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
GetCommentStr(ACell)
|
|
]));
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteBool(AStream: TStream;
|
|
const ARow, ACol: Cardinal; const AValue: boolean; ACell: PCell);
|
|
begin
|
|
Unused(ARow, ACol);
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + // colIndex, style, formula, hyperlink, merge
|
|
'<Data ss:Type="%s">' + // data type
|
|
'%s' + // value string
|
|
'</Data>' +
|
|
'%s' + // Comment <Comment>...</Comment>
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
StrUtils.IfThen(HasFormula(ACell), GetCellContentTypeStr(ACell), 'Boolean'),
|
|
StrUtils.IfThen(AValue, '1', '0'),
|
|
GetCommentStr(ACell)
|
|
]));
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.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);
|
|
cctFormula:
|
|
WriteFormula(AStream, ACell^.Row, ACell^.Col, ACell);
|
|
end;
|
|
|
|
if (FWorksheet as TsWorksheet).ReadComment(ACell) <> '' then
|
|
WriteComment(AStream, ACell);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteCellNodes(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet; ARow: Cardinal);
|
|
var
|
|
c: Cardinal;
|
|
cell: PCell;
|
|
lCell: TCell;
|
|
styleCell: PCell;
|
|
value: variant;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
if (boVirtualMode in FWorkbook.Options) and (not Assigned(sheet.OnWriteCellData)) then
|
|
exit;
|
|
|
|
FPrevCol := UNASSIGNED_ROW_COL_INDEX;
|
|
for c := 0 to FLastCol do
|
|
begin
|
|
if (boVirtualMode in FWorkbook.Options) then begin
|
|
lCell.Row := ARow; // to silence a compiler hint
|
|
InitCell(lCell);
|
|
value := varNull;
|
|
styleCell := nil;
|
|
sheet.OnWriteCellData(sheet, ARow, c, value, styleCell);
|
|
if styleCell <> nil then
|
|
lCell := styleCell^;
|
|
lCell.Row := ARow;
|
|
lCell.Col := c;
|
|
if VarIsNull(value) then
|
|
begin
|
|
if styleCell <> nil then
|
|
lCell.ContentType := cctEmpty
|
|
else
|
|
Continue;
|
|
end else
|
|
if VarIsNumeric(value) then
|
|
begin
|
|
lCell.ContentType := cctNumber;
|
|
lCell.NumberValue := value;
|
|
end else
|
|
if VarType(value) = varDate then
|
|
begin
|
|
lCell.ContentType := cctDateTime;
|
|
lCell.DateTimeValue := StrToDateTime(VarToStr(value), Workbook.FormatSettings); // was: StrToDate
|
|
end else
|
|
if VarIsStr(value) then
|
|
begin
|
|
lCell.ContentType := cctUTF8String;
|
|
lCell.UTF8StringValue := VarToStrDef(value, '');
|
|
end else
|
|
if VarIsBool(value) then
|
|
begin
|
|
lCell.ContentType := cctBool;
|
|
lCell.BoolValue := value <> 0;
|
|
end;
|
|
WriteCellToStream(AStream, @lCell);
|
|
varClear(value);
|
|
FPrevCol := c;
|
|
end else
|
|
begin
|
|
// Normal mode
|
|
cell := sheet.Findcell(ARow, c);
|
|
if cell <> nil then
|
|
begin
|
|
if sheet.IsMerged(cell) and not sheet.IsMergeBase(cell) then
|
|
Continue;
|
|
WriteCellToStream(AStream, cell);
|
|
FPrevCol := c;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteColumns(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
c, c1, c2: Cardinal;
|
|
colwidthStr: String;
|
|
styleStr: String;
|
|
hiddenStr: String;
|
|
col: PCol;
|
|
begin
|
|
c1 := 0;
|
|
c2 := TsWorksheet(AWorksheet).GetLastColIndex;
|
|
FPrevCol := UNASSIGNED_ROW_COL_INDEX;
|
|
for c := c1 to c2 do
|
|
begin
|
|
col := TsWorksheet(AWorksheet).FindCol(c);
|
|
styleStr := '';
|
|
colWidthStr := '';
|
|
hiddenStr := '';
|
|
|
|
if Assigned(col) then
|
|
begin
|
|
// column width is needed in pts.
|
|
if col^.ColWidthType = cwtCustom then
|
|
colwidthStr := Format(' ss:Width="%0.2f" ss:AutoFitWidth="0"',
|
|
[(FWorkbook as TsWorkbook).ConvertUnits(col^.Width, FWorkbook.Units, suPoints)],
|
|
FPointSeparatorSettings);
|
|
// column style
|
|
if col^.FormatIndex > 0 then
|
|
styleStr := GetStyleStr(col^.FormatIndex);
|
|
end;
|
|
|
|
if TsWorksheet(AWorksheet).ColHidden(c) then
|
|
hiddenStr := ' ss:Hidden="1"';
|
|
|
|
if (colWidthStr <> '') or (stylestr <> '') or (hiddenstr <> '') then begin
|
|
AppendToStream(AStream, COL_INDENT + Format(
|
|
'<Column%s%s%s%s />' + LF, [GetIndexStr(c, FPrevCol), colWidthStr, styleStr, hiddenStr]));
|
|
FPrevCol := c;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteConditionalFormat(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet; AFormat: TsConditionalFormat);
|
|
|
|
function BackgroundStyle(AFormat: TsCellFormat): String;
|
|
begin
|
|
Result := '';
|
|
if not (uffBackground in AFormat.UsedFormattingFields) then
|
|
exit;
|
|
if AFormat.Background.Style = fsSolidFill then
|
|
Result := Format('background:%s;', [ColorToHTMLColorStr(AFormat.Background.BgColor)])
|
|
else
|
|
Result := Format('background:%s;mso-pattern:%s %s;', [
|
|
ColorToHTMLColorStr(AFormat.Background.BgColor),
|
|
CF_FILL_NAMES[AFormat.Background.Style],
|
|
ColorToHTMLColorStr(AFormat.Background.FgColor)
|
|
]);
|
|
end;
|
|
|
|
function BorderStyle(AFormat: TsCellFormat): String;
|
|
var
|
|
cb: TsCellBorder;
|
|
allEqual: Boolean;
|
|
bs: TsCellBorderStyle;
|
|
begin
|
|
Result := '';
|
|
if not (uffBorder in AFormat.UsedFormattingFields) then
|
|
exit;
|
|
allEqual := ([cbEast, cbWest, cbNorth, cbSouth] = AFormat.Border);
|
|
if allEqual then begin
|
|
bs := AFormat.BorderStyles[cbEast];
|
|
for cb in TsCellBorders do
|
|
if not (cb in [cbDiagUp, cbDiagDown]) then
|
|
if (AFormat.BorderStyles[cb].Color <> bs.Color) or
|
|
(AFormat.BorderStyles[cb].LineStyle <> bs.LineStyle) then
|
|
begin
|
|
allEqual := false;
|
|
break;
|
|
end;
|
|
end;
|
|
if allEqual then
|
|
Result := Format('border:0.5pt %s %s;', [
|
|
CF_LINE_STYLES[bs.LineStyle],
|
|
ColorToHTMLColorStr(bs.Color)
|
|
])
|
|
else
|
|
for cb in TsCellBorders do
|
|
begin
|
|
if cb in [cbDiagUp, cbDiagDown] then
|
|
Continue;
|
|
bs := AFormat.BorderStyles[cb];
|
|
if (cb in AFormat.Border) then
|
|
Result := Result + Format('border-%s:0.5pt %s %s;', [
|
|
Lowercase(BORDER_NAMES[cb]),
|
|
CF_LINE_STYLES[bs.LineStyle],
|
|
ColorToHTMLColorStr(bs.Color)
|
|
]);
|
|
end;
|
|
end;
|
|
|
|
function FontStyle(AFormat: TsCellFormat): String;
|
|
var
|
|
fnt: TsFont;
|
|
begin
|
|
Result := '';
|
|
if not (uffFont in AFormat.UsedFormattingFields) then
|
|
exit;
|
|
fnt := TsWorkbook(FWorkbook).GetFont(AFormat.FontIndex);
|
|
if (fssItalic in fnt.Style) then
|
|
Result := Result + 'font-style:italic;';
|
|
if (fssBold in fnt.Style) then
|
|
Result := Result + 'font-weight:700;';
|
|
if (fssStrikeOut in fnt.Style) then
|
|
Result := Result + 'text-line-through:single;';
|
|
if fnt.Color <> scNotDefined then
|
|
Result := Result + 'color:' + ColorToHTMLColorStr(fnt.Color) + ';';
|
|
end;
|
|
|
|
var
|
|
rangeStr: String;
|
|
cfRule: TsCFCellRule;
|
|
i: Integer;
|
|
value1Str, value2Str: String;
|
|
sheet: TsWorksheet;
|
|
book: TsWorkbook;
|
|
fmt: TsCellFormat;
|
|
s: String;
|
|
needToExit: Boolean;
|
|
begin
|
|
book := TsWorkbook(FWorkbook);
|
|
sheet := TsWorksheet(AWorksheet);
|
|
|
|
needToExit := false;
|
|
for i := 0 to AFormat.RulesCount-1 do
|
|
if not (AFormat.Rules[i] is TsCFCellRule) then
|
|
begin
|
|
FWorkbook.AddErrorMsg('Conditional formatting rule ' + AFormat.Rules[i].ClassName + ' not supported by Excel-XML.');
|
|
needToExit := true;
|
|
end;
|
|
|
|
if needToExit then
|
|
exit;
|
|
|
|
AppendToStream(AStream, INDENT2 +
|
|
'<ConditionalFormatting xmlns="urn:schemas-microsoft-com:office:excel">');
|
|
|
|
with AFormat.CellRange do
|
|
rangeStr := GetCellRangeString_R1C1(Row1, Col1, Row2, Col2, [], Row1, Col1);
|
|
AppendToStream(AStream, LF + INDENT3 +
|
|
'<Range>' + rangeStr + '</Range>');
|
|
|
|
for i := 0 to AFormat.RulesCount-1 do
|
|
begin
|
|
if AFormat.Rules[i] is TsCFCellRule then
|
|
begin
|
|
cfRule := TsCFCellRule(AFormat.Rules[i]);
|
|
if CF_CONDITIONS[cfRule.Condition] = '' then
|
|
begin
|
|
s := GetEnumName(TypeInfo(TsCFCondition), Ord(cfRule.Condition));
|
|
FWorkbook.AddErrorMsg('Conditional formatting rule "' + s + '" not supported by ExcelXML.');
|
|
Continue;
|
|
end;
|
|
|
|
if cfRule.Condition = cfcExpression then
|
|
begin
|
|
s := cfRule.Operand1;
|
|
if (s <> '') and (s[1] <> '=') then s := '=' + s;
|
|
value1Str := CFOperandToStr(s, sheet);
|
|
value2Str := '';
|
|
end else
|
|
begin
|
|
value1Str := CFOperandToStr(cfRule.Operand1, sheet);
|
|
value2Str := CFOperandToStr(cfRule.Operand2, sheet);
|
|
end;
|
|
|
|
s := CF_CONDITIONS[cfRule.Condition];
|
|
if s[1] = '@' then
|
|
begin
|
|
Delete(s, 1,1);
|
|
if s = '' then
|
|
s := value1Str
|
|
else
|
|
s := Format(s, [value1Str, value2Str, rangeStr]);
|
|
value1Str := s;
|
|
s := '';
|
|
end;
|
|
|
|
AppendToStream(AStream, LF + INDENT3 +
|
|
'<Condition>');
|
|
|
|
if s <> '' then
|
|
AppendToStream(AStream, LF + INDENT4 +
|
|
'<Qualifier>' + s + '</Qualifier>');
|
|
|
|
if value1Str <> '' then
|
|
AppendToStream(AStream, LF + INDENT4 +
|
|
'<Value1>' + value1Str + '</Value1>');
|
|
|
|
if (cfRule.Condition in [cfcBetween, cfcNotBetween]) and (value2Str <> '') then
|
|
AppendToStream(AStream, LF + INDENT4 +
|
|
'<Value2>' + value2Str + '</Value2>');
|
|
|
|
fmt := book.GetCellFormat(cfRule.FormatIndex);
|
|
s := BackgroundStyle(fmt) + BorderStyle(fmt) + FontStyle(fmt);
|
|
if s <> '' then
|
|
begin
|
|
if s[Length(s)] = ';' then Delete(s, Length(s), 1);
|
|
AppendToStream(AStream, LF + INDENT4 +
|
|
'<Format Style=''' + s + '''/>');
|
|
end;
|
|
|
|
AppendToStream(AStream, LF + INDENT3 +
|
|
'</Condition>'
|
|
);
|
|
end;
|
|
end;
|
|
|
|
AppendToStream(AStream, LF + INDENT2 +
|
|
'</ConditionalFormatting>' + LF);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteConditionalFormatting(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
book: TsWorkbook;
|
|
cf: TsConditionalFormat;
|
|
i: Integer;
|
|
begin
|
|
book := TsWorkbook(FWorkbook);
|
|
for i := 0 to book.GetNumConditionalFormats-1 do
|
|
begin
|
|
cf := book.GetConditionalFormat(i);
|
|
WriteConditionalFormat(AStream, AWorksheet, cf);
|
|
end;
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteCustomDocumentProperties(AStream: TStream);
|
|
{ <CustomDocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
|
|
<Comparny dt:dt="string">Disney</Comparny>
|
|
<Status dt:dt="string">finished</Status>
|
|
</CustomDocumentProperties> }
|
|
var
|
|
book: TsWorkbook;
|
|
i: Integer;
|
|
begin
|
|
book := TsWorkbook(FWorkbook);
|
|
if book.MetaData.Custom.Count = 0 then
|
|
exit;
|
|
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<CustomDocumentProperties xmlns="urn:schemas-microsoft-com:office:office">' + LF);
|
|
|
|
for i := 0 to book.MetaData.Custom.Count-1 do
|
|
AppendToStream(AStream, Format(INDENT2 +
|
|
'<%0:s dt:dt="string">%1:s</%0:s>' + LF, [
|
|
book.MetaData.Custom.Names[i],
|
|
UTF8TextToXMLText(book.MetaData.Custom.ValueFromIndex[i])
|
|
]));
|
|
|
|
AppendToStream(AStream, INDENT1 +
|
|
'</CustomDocumentProperties>' + LF);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteDateTime(AStream: TStream;
|
|
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
|
var
|
|
valueStr: String = '';
|
|
ExcelDate: TDateTime;
|
|
begin
|
|
Unused(ARow, ACol);
|
|
|
|
ExcelDate := ConvertDateTimeToExcelDateTime(AValue, FDateMode);
|
|
valueStr := FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz', ExcelDate);
|
|
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + LF + VALUE_INDENT + // colIndex, style, formula, hyperlink, merge
|
|
'<Data ss:Type="%s">' + // data type
|
|
'%s' + // value string
|
|
'</Data>' + LF + CELL_INDENT +
|
|
'%s' + // Comment <Comment>...</Comment>
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
StrUtils.IfThen(HasFormula(ACell), GetCellContentTypeStr(ACell), 'DateTime'),
|
|
valueStr,
|
|
GetCommentStr(ACell)
|
|
]));
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteDocumentProperties(AStream: TStream);
|
|
var
|
|
sTitle: String;
|
|
sSubject: String;
|
|
sAuthor: String;
|
|
sLastAuthor: String;
|
|
sDateCreated: String;
|
|
sDateLastSaved: String;
|
|
book: TsWorkbook;
|
|
dt: TDateTime;
|
|
begin
|
|
book := TsWorkbook(FWorkbook);
|
|
|
|
if book.MetaData.IsEmpty then
|
|
begin
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office" />' + LF);
|
|
exit;
|
|
end;
|
|
|
|
if book.MetaData.Title <> '' then
|
|
sTitle := '<Title>' + UTF8TextToXMLText(book.MetaData.Title) + '</Title>' + LF + INDENT2
|
|
else
|
|
sTitle := '';
|
|
|
|
if book.MetaData.Subject <> '' then
|
|
sSubject := '<Subject>' + UTF8TextToXMLText(book.MetaData.Subject) + '</Subject>' + LF + INDENT2
|
|
else
|
|
sSubject := '';
|
|
|
|
if book.MetaData.CreatedBy <> '' then
|
|
sAuthor := '<Author>' + UTF8TextToXMLText(book.MetaData.CreatedBy) + '</Author>' + LF + INDENT2
|
|
else
|
|
sAuthor := '';
|
|
|
|
if book.MetaData.LastModifiedBy <> '' then
|
|
sLastAuthor := '<LastAuthor>' + UTF8TextToXMLText(book.MetaData.LastModifiedBy) + '</LastAuthor>' + LF + INDENT2
|
|
else
|
|
sLastAuthor := '';
|
|
|
|
// Dates are UTC and in format YYYY-mm-ddThh:nn:ssZ
|
|
if book.MetaData.DateCreated > 0 then begin
|
|
dt := book.MetaData.DateCreated + GetLocalTimeOffset / (24*60);
|
|
sDateCreated := FormatDateTime(ISO8601FormatExtendedUTC, dt);
|
|
sDateCreated := '<Created>' + sDateCreated + '</Created>' + LF + INDENT2;
|
|
end else
|
|
sDateCreated := '';
|
|
|
|
if book.MetaData.DateLastModified > 0 then
|
|
begin
|
|
dt := book.MetaData.DateLastModified + GetLocalTimeOffset / (24*60);
|
|
sDateLastSaved := FormatDateTime(ISO8601FormatExtendedUTC, dt);
|
|
sDateLastSaved := '<LastSaved>' + sDateLastSaved + '</LastSaved>' + LF + INDENT2;
|
|
end else
|
|
sDateLastSaved := '';
|
|
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">' + LF + INDENT2 +
|
|
sTitle +
|
|
sSubject +
|
|
sAuthor +
|
|
sLastAuthor +
|
|
sDateCreated +
|
|
sDateLastSaved +
|
|
'<Version>16.00</Version>' + LF + INDENT1 +
|
|
'</DocumentProperties>' + LF
|
|
);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteError(AStream: TStream;
|
|
const ARow, ACol: Cardinal; const AValue: TsErrorValue; ACell: PCell);
|
|
begin
|
|
Unused(ARow, ACol);
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + LF + VALUE_INDENT + // colIndex, style, formula, hyperlink, merge
|
|
'<Data ss:Type="%s">' + // data type
|
|
'%s' + // value string
|
|
'</Data>' + LF + CELL_INDENT +
|
|
'%s' + // Comment <Comment>...</Comment>
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
StrUtils.IfThen(HasFormula(ACell), GetCellContentTypeStr(ACell), 'Error'),
|
|
GetErrorValueStr(AValue),
|
|
GetCommentStr(ACell)
|
|
]));
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteExcelWorkbook(AStream: TStream);
|
|
var
|
|
datemodeStr: String;
|
|
protectStr: String;
|
|
begin
|
|
if FDateMode = dm1904 then
|
|
datemodeStr := INDENT2 + '<Date1904/>' + LF else
|
|
datemodeStr := '';
|
|
|
|
protectStr := Format(
|
|
'<ProtectStructure>%s</ProtectStructure>' + LF + INDENT2 +
|
|
'<ProtectWindows>%s</ProtectWindows>' + LF, [
|
|
FALSE_TRUE[bpLockStructure in Workbook.Protection],
|
|
FALSE_TRUE[bpLockWindows in Workbook.Protection]
|
|
]);
|
|
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">' + LF +
|
|
datemodeStr + INDENT2 +
|
|
protectStr + INDENT1 +
|
|
'</ExcelWorkbook>' + LF);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteFormula(AStream: TStream;
|
|
const ARow, ACol: Cardinal; ACell: PCell);
|
|
var
|
|
xmlnsStr: String;
|
|
dataTagStr: String;
|
|
begin
|
|
Unused(ARow);
|
|
Unused(ACol);
|
|
|
|
if ACell^.ContentType <> cctFormula then
|
|
raise Exception.Create('WriteFormula called for calculated cell.');
|
|
|
|
xmlnsStr := ' xmlns="http://www.w3.org/TR/REC-html40"';
|
|
dataTagStr := ''; // or 'ss:' -- to do...
|
|
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + LF + VALUE_INDENT + // colIndex, style, formula, hyperlink, merge
|
|
'<%sData%s>'+ // "ss:", data type, "xmlns=.."
|
|
'</%sData>' + LF + CELL_INDENT + // "ss:"
|
|
'%s' + // Comment
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACell^.Col, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
dataTagStr, xmlnsStr,
|
|
dataTagStr,
|
|
GetCommentStr(ACell)
|
|
]));
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteLabel(AStream: TStream; const ARow,
|
|
ACol: Cardinal; const AValue: string; ACell: PCell);
|
|
const
|
|
MAXBYTES = 32767; // limit for this format
|
|
var
|
|
valueStr: String;
|
|
cctStr: String;
|
|
xmlnsStr: String;
|
|
dataTagStr: String;
|
|
p: Integer;
|
|
tmp: String;
|
|
ResultingValue: String;
|
|
begin
|
|
// Office 2007-2010 (at least) supports no more characters in a cell;
|
|
if Length(AValue) > MAXBYTES then
|
|
begin
|
|
ResultingValue := Copy(AValue, 1, MAXBYTES); //may chop off multicodepoint UTF8 characters but well...
|
|
Workbook.AddErrorMsg(rsTruncateTooLongCellText, [
|
|
MAXBYTES, GetCellString(ARow, ACol)
|
|
]);
|
|
end else
|
|
resultingValue := AValue;
|
|
|
|
{ Check for invalid characters }
|
|
if not ValidXMLText(ResultingValue) then
|
|
Workbook.AddErrorMsg(
|
|
rsInvalidCharacterInCell, [
|
|
GetCellString(ARow, ACol)
|
|
]);
|
|
|
|
if Length(ACell^.RichTextParams) > 0 then
|
|
begin
|
|
RichTextToHTML(
|
|
FWorkbook as TsWorkbook,
|
|
(FWorksheet as TsWorksheet).ReadCellFont(ACell),
|
|
ResultingValue,
|
|
ACell^.RichTextParams,
|
|
valueStr, // html-formatted rich text
|
|
'html:', tcProperCase
|
|
);
|
|
xmlnsStr := ' xmlns="http://www.w3.org/TR/REC-html40"';
|
|
dataTagStr := 'ss:';
|
|
|
|
// Excel does not like units in font size specification...
|
|
tmp := valueStr;
|
|
p := pos('<Font html:Size="', valueStr);
|
|
if p > 0 then begin
|
|
valueStr := '';
|
|
while p > 0 do begin
|
|
inc(p, Length('<Font html:Size="'));
|
|
valueStr := valueStr + copy(tmp, 1, p-1);
|
|
while (tmp[p] <> '"') do begin
|
|
if (tmp[p] in ['0'..'9', '.']) then valueStr := valueStr + tmp[p];
|
|
inc(p);
|
|
end;
|
|
tmp := copy(tmp, p, MaxInt);
|
|
p := pos('<Font html:Size="', tmp);
|
|
end;
|
|
valueStr := valuestr + tmp;
|
|
end;
|
|
end else
|
|
begin
|
|
valueStr := ResultingValue;
|
|
if not ValidXMLText(valueStr, true, true) then
|
|
Workbook.AddErrorMsg(
|
|
rsInvalidCharacterInCell, [
|
|
GetCellString(ARow, ACol)
|
|
]);
|
|
xmlnsStr := '';
|
|
dataTagStr := '';
|
|
end;
|
|
|
|
cctStr := 'String';
|
|
if HasFormula(ACell) then
|
|
cctStr := GetCellContentTypeStr(ACell) else
|
|
cctStr := 'String';
|
|
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + LF + VALUE_INDENT + // colIndex, style, formula, hyperlink, merge
|
|
'<%sData ss:Type="%s"%s>'+ // "ss:", data type, "xmlns=.."
|
|
'%s' + // value string
|
|
'</%sData>' + LF + CELL_INDENT + // "ss:"
|
|
'%s' + // Comment
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
dataTagStr, cctStr, xmlnsStr,
|
|
valueStr,
|
|
dataTagStr,
|
|
GetCommentStr(ACell)
|
|
]));
|
|
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 +
|
|
'<NamedRange ss:Name="Print_Area" ss:RefersTo="' + print_range_str + '"/>' + LF;
|
|
|
|
// Repeated columns --> Name "Print_Titles"
|
|
if (RepeatedCols.FirstIndex <> UNASSIGNED_ROW_COL_INDEX) and
|
|
(RepeatedCols.LastIndex <> UNASSIGNED_ROW_COL_INDEX)
|
|
then begin
|
|
s := 'C' + {%H-}IntToStr(RepeatedCols.FirstIndex + 1);
|
|
if RepeatedCols.FirstIndex <> RepeatedCols.LastIndex then
|
|
s := s + ':C' + {%H-}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' + {%H-}IntToStr(RepeatedRows.FirstIndex + 1);
|
|
if RepeatedRows.FirstIndex <> RepeatedRows.LastIndex then
|
|
s := s + ':R' + {%H-}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 +
|
|
'<NamedRange ss:Name="Print_Titles" ss:RefersTo="' + print_titles_str + '"/>' + LF;
|
|
end;
|
|
|
|
if (print_range_str = '') and (print_titles_str = '') then
|
|
exit;
|
|
|
|
AppendToStream(AStream, NAMES_INDENT +
|
|
'<Names>' + LF +
|
|
print_titles_str + NAMES_INDENT +
|
|
print_range_str + NAMES_INDENT +
|
|
'</Names>' + LF);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
const AValue: double; ACell: PCell);
|
|
begin
|
|
Unused(ARow, ACol);
|
|
AppendToStream(AStream, Format(CELL_INDENT +
|
|
'<Cell%s%s%s%s%s>' + LF + VALUE_INDENT + // colIndex, style, formula, hyperlink, merge
|
|
'<Data ss:Type="%s">' + // data type
|
|
'%g' + // value
|
|
'</Data>' + LF + CELL_INDENT +
|
|
'%s' + // Comment <Comment>...</Comment>
|
|
'</Cell>' + LF, [
|
|
GetIndexStr(ACol, FPrevCol), GetStyleStr(ACell^.FormatIndex), GetFormulaStr(ACell),
|
|
GetHyperlinkStr(ACell), GetMergeStr(ACell),
|
|
StrUtils.IfThen(HasFormula(ACell), GetCellContentTypeStr(ACell), 'Number'),
|
|
AValue,
|
|
GetCommentStr(ACell)], FPointSeparatorSettings)
|
|
);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteOfficeDocumentSettings(AStream: TStream);
|
|
begin
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">' + LineEnding + INDENT2 +
|
|
'<AllowPNG/>' + LineEnding + INDENT1 +
|
|
'</OfficeDocumentSettings>' + LineEnding
|
|
);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WritePageBreaks(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
i: Integer;
|
|
nc, nr: Integer;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
s: String;
|
|
col: PCol;
|
|
row: PRow;
|
|
begin
|
|
nc := 0;
|
|
for i := 0 to sheet.Cols.Count - 1 do
|
|
if (croPageBreak in PCol(sheet.Cols[i])^.Options) then inc(nc);
|
|
|
|
nr := 0;
|
|
for i:= 0 to sheet.Rows.Count - 1 do
|
|
if (croPageBreak in PRow(sheet.Rows[i])^.Options) then inc(nr);
|
|
|
|
if (nc = 0) and (nr = 0) then
|
|
exit;
|
|
|
|
s := INDENT2 +
|
|
'<PageBreaks xmlns="urn:schemas-microsoft-com:office:excel">' + LF;
|
|
|
|
if nc > 0 then begin
|
|
s := s + INDENT3 +
|
|
'<ColBreaks>' + LF;
|
|
for i := 0 to sheet.Cols.Count - 1 do begin
|
|
col := PCol(sheet.Cols[i]);
|
|
if (croPageBreak in col^.Options) then
|
|
s := s + INDENT4 +
|
|
'<ColBreak>' + LF + INDENT5 +
|
|
'<Column>' + IntToStr(col^.Col) + '</Column>' + LF + INDENT4 +
|
|
'</ColBreak>' + LF;
|
|
end;
|
|
s := s + INDENT3 +
|
|
'</ColBreaks>' + LF;
|
|
end;
|
|
|
|
if nr > 0 then begin
|
|
s := s + INDENT3 +
|
|
'<RowBreaks>' + LF;
|
|
for i := 0 to sheet.Rows.Count - 1 do begin
|
|
row := PRow(sheet.Rows[i]);
|
|
if (croPageBreak in row^.Options) then
|
|
s := s + INDENT4 +
|
|
'<RowBreak>' + LF + INDENT5 +
|
|
'<Row>' + IntToStr(row^.Row) + '</Row>' + LF + INDENT4 +
|
|
'</RowBreak>' + LF;
|
|
end;
|
|
s := s + INDENT3 +
|
|
'</RowBreaks>' + LF;
|
|
end;
|
|
|
|
s := s + INDENT2 +
|
|
'</PageBreaks>' + LF;
|
|
|
|
AppendToStream(AStream, s);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteRows(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
c: Cardinal;
|
|
r: Cardinal;
|
|
rowheightStr: String;
|
|
hiddenStr: String;
|
|
styleStr: String;
|
|
s: String;
|
|
row: PRow;
|
|
cell: PCell;
|
|
hasCells: Boolean;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
FPrevRow := UNASSIGNED_ROW_COL_INDEX;
|
|
for r := 0 to FLastRow do
|
|
begin
|
|
row := sheet.FindRow(r);
|
|
styleStr := '';
|
|
hiddenStr := '';
|
|
// Row height is needed in pts.
|
|
if Assigned(row) then
|
|
begin
|
|
rowheightStr := Format(' ss:Height="%.2f"',
|
|
[(FWorkbook as TsWorkbook).ConvertUnits(row^.Height, FWorkbook.Units, suPoints)],
|
|
FPointSeparatorSettings
|
|
);
|
|
if row^.RowHeightType = rhtCustom then
|
|
rowHeightStr := ' ss:AutoFitHeight="0"' + rowHeightStr
|
|
else
|
|
rowHeightStr := ' ss:AutoFitHeight="1"' + rowHeightStr;
|
|
if row^.FormatIndex > 0 then
|
|
styleStr := GetStyleStr(row^.FormatIndex);
|
|
end else
|
|
rowheightStr := ' ss:AutoFitHeight="1"';
|
|
|
|
if sheet.RowHidden(r) then
|
|
hiddenStr := ' ss:Hidden="1"';
|
|
|
|
if boVirtualMode in FWorkbook.Options then
|
|
hasCells := true
|
|
else begin
|
|
hasCells := false;
|
|
for c := 0 to FLastCol do begin
|
|
cell := sheet.FindCell(r, c);
|
|
if cell <> nil then begin
|
|
hasCells := true;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
s := Format('%s%s%s%s', [GetIndexStr(r, FPrevRow), rowheightStr, styleStr, hiddenStr]);
|
|
if hasCells then begin
|
|
AppendToStream(AStream, ROW_INDENT + Format(
|
|
'<Row%s>', [s]) + LF);
|
|
WriteCellNodes(AStream, AWorksheet, r);
|
|
AppendToStream(AStream, ROW_INDENT +
|
|
'</Row>' + LF);
|
|
FPrevRow := r;
|
|
end else
|
|
if (rowheightStr <> '') or (styleStr <> '') or (hiddenStr <> '') then begin
|
|
AppendToStream(AStream, ROW_INDENT + Format(
|
|
'<Row%s/>', [s]) + LF);
|
|
FPrevRow := r;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteStyle(AStream: TStream; AIndex: Integer);
|
|
var
|
|
fmt: PsCellFormat;
|
|
deffnt, fnt: TsFont;
|
|
s, fmtVert, fmtHor, fmtWrap, fmtRot: String;
|
|
nfp: TsNumFormatParams;
|
|
nfs: String;
|
|
fill: TsFillPattern;
|
|
cb: TsCellBorder;
|
|
cbs: TsCellBorderStyle;
|
|
book: TsWorkbook;
|
|
begin
|
|
book := FWorkbook as TsWorkbook;
|
|
deffnt := book.GetDefaultFont;
|
|
if AIndex = 0 then
|
|
begin
|
|
AppendToStream(AStream, Format(INDENT2 +
|
|
'<Style ss:ID="Default" ss:Name="Normal">' + LF + INDENT3 +
|
|
'<Aligment ss:Vertical="Bottom" />' + LF + INDENT3 +
|
|
'<Borders />' + LF + INDENT3 +
|
|
'<Font ss:FontName="%s" x:Family="Swiss" ss:Size="%d" ss:Color="%s" />' + LF + INDENT3 +
|
|
'<Interior />' + LF + INDENT3 +
|
|
'<NumberFormat />' + LF + INDENT3 +
|
|
'<Protection />' + LF + INDENT2 +
|
|
'</Style>' + LF,
|
|
[deffnt.FontName, round(deffnt.Size), ColorToHTMLColorStr(deffnt.Color)] )
|
|
)
|
|
end else
|
|
begin
|
|
AppendToStream(AStream, Format(INDENT2 +
|
|
'<Style ss:ID="s%d">' + LF, [AIndex + FMT_OFFSET]));
|
|
|
|
fmt := book.GetPointerToCellFormat(AIndex);
|
|
|
|
// Horizontal alignment
|
|
fmtHor := '';
|
|
if uffHorAlign in fmt^.UsedFormattingFields then
|
|
case fmt^.HorAlignment of
|
|
haDefault: ;
|
|
haLeft : fmtHor := 'ss:Horizontal="Left" ';
|
|
haCenter : fmtHor := 'ss:Horizontal="Center" ';
|
|
haRight : fmtHor := 'ss:Horizontal="Right" ';
|
|
else
|
|
raise EFPSpreadsheetWriter.Create('[TsSpreadXMLWriter.WriteStyle] Horizontal alignment cannot be handled.');
|
|
end;
|
|
|
|
// Vertical alignment
|
|
fmtVert := '';
|
|
if uffVertAlign in fmt^.UsedFormattingFields then
|
|
case fmt^.VertAlignment of
|
|
vaDefault: ;
|
|
vaTop : fmtVert := 'ss:Vertical="Top" ';
|
|
vaCenter : fmtVert := 'ss:Vertical="Center" ';
|
|
vaBottom : fmtVert := 'ss:Vertical="Bottom" ';
|
|
else
|
|
raise EFPSpreadsheetWriter.Create('[TsSpreadXMLWriter.WriteStyle] Vertical alignment cannot be handled.');
|
|
end;
|
|
|
|
// Wrap text
|
|
if uffWordwrap in fmt^.UsedFormattingFields then
|
|
fmtWrap := 'ss:WrapText="1" ' else
|
|
fmtWrap := '';
|
|
|
|
// Text rotation
|
|
fmtRot := '';
|
|
if uffTextRotation in fmt^.UsedFormattingFields then
|
|
case fmt^.TextRotation of
|
|
rt90DegreeClockwiseRotation : fmtRot := 'ss:Rotate="-90" ';
|
|
rt90DegreeCounterClockwiseRotation : fmtRot := 'ss:Rotate="90" ';
|
|
rtStacked : fmtRot := 'ss:VerticalText="1" ';
|
|
end;
|
|
|
|
// Write all the alignment, text rotation and wordwrap attributes to stream
|
|
AppendToStream(AStream, Format(INDENT3 +
|
|
'<Alignment %s%s%s%s />' + LF,
|
|
[fmtHor, fmtVert, fmtWrap, fmtRot])
|
|
);
|
|
|
|
// Font
|
|
if (uffFont in fmt^.UsedFormattingFields) then
|
|
begin
|
|
fnt := book.GetFont(fmt^.FontIndex);
|
|
s := '';
|
|
if fnt.FontName <> deffnt.FontName then
|
|
s := s + Format('ss:FontName="%s" ', [XMLQuote(fnt.FontName)]);
|
|
if not SameValue(fnt.Size, deffnt.Size, 1E-3) then
|
|
s := s + Format('ss:Size="%g" ', [fnt.Size], FPointSeparatorSettings);
|
|
if fnt.Color <> deffnt.Color then
|
|
s := s + Format('ss:Color="%s" ', [ColorToHTMLColorStr(fnt.Color)]);
|
|
if fssBold in fnt.Style then
|
|
s := s + 'ss:Bold="1" ';
|
|
if fssItalic in fnt.Style then
|
|
s := s + 'ss:Italic="1" ';
|
|
if fssUnderline in fnt.Style then
|
|
s := s + 'ss:Underline="Single" '; // or "Double", not supported by fps
|
|
if fssStrikeout in fnt.Style then
|
|
s := s + 'ss:StrikeThrough="1" ';
|
|
if s <> '' then
|
|
AppendToStream(AStream, INDENT3 +
|
|
'<Font ' + s + '/>' + LF);
|
|
end;
|
|
|
|
// Number Format
|
|
if (uffNumberFormat in fmt^.UsedFormattingFields) then
|
|
begin
|
|
nfp := book.GetNumberFormat(fmt^.NumberFormatIndex);
|
|
nfp.AllowLocalizedAMPM := false; // Replace "AMPM" by "AM/PM"
|
|
nfs := nfp.NumFormatStr;
|
|
AppendToStream(AStream, Format(INDENT3 +
|
|
'<NumberFormat ss:Format="%s"/>' + LF, [XMLQuote(nfs)])); // Do not UTF8TextToXMLText(nfs) because of '%'
|
|
end;
|
|
|
|
// Background
|
|
if (uffBackground in fmt^.UsedFormattingFields) then
|
|
begin
|
|
fill := fmt^.Background;
|
|
if fill.Style = fsNoFill then
|
|
AppendToStream(AStream, INDENT3 + '<Interior />' + LF)
|
|
else begin
|
|
if fill.Style = fsSolidFill then
|
|
s := 'ss:Color="' + ColorToHtmlColorStr(fill.FgColor) + '" '
|
|
else
|
|
s := Format('ss:Color="%s" ss:PatternColor="%s" ', [
|
|
ColorToHTMLColorStr(fill.BgColor),
|
|
ColorToHTMLColorStr(fill.FgColor)
|
|
]);
|
|
s := s + 'ss:Pattern="' + FILL_NAMES[fill.Style] + '" ';
|
|
AppendToStream(AStream, INDENT3 +
|
|
'<Interior ' + s + '/>' + LF)
|
|
end;
|
|
end;
|
|
|
|
// Borders
|
|
if (uffBorder in fmt^.UsedFormattingFields) then
|
|
begin
|
|
s := '';
|
|
for cb in TsCellBorder do
|
|
if cb in fmt^.Border then begin
|
|
cbs := fmt^.BorderStyles[cb];
|
|
s := s + INDENT4 + Format('<Border ss:Position="%s" ss:LineStyle="%s"', [
|
|
BORDER_NAMES[cb], LINE_STYLES[cbs.LineStyle]]);
|
|
if fmt^.BorderStyles[cb].LineStyle <> lsHair then
|
|
s := Format('%s ss:Weight="%d"', [s, LINE_WIDTHS[cbs.LineStyle]]);
|
|
s := Format('%s ss:Color="%s"', [s, ColorToHTMLColorStr(cbs.Color)]);
|
|
s := s + '/>' + LF;
|
|
end;
|
|
if s <> '' then
|
|
AppendToStream(AStream, INDENT3 +
|
|
'<Borders>' + LF + s + INDENT3 +
|
|
'</Borders>' + LF);
|
|
end;
|
|
|
|
// Protection
|
|
s := '';
|
|
if not (cpLockCell in fmt^.Protection) then
|
|
s := s + 'ss:Protected="0" ';
|
|
if cpHideFormulas in fmt^.Protection then
|
|
s := s + 'x:HideFormula="1" ';
|
|
if s <> '' then
|
|
AppendToStream(AStream, INDENT3 +
|
|
'<Protection ' + s + '/>' + LF);
|
|
|
|
AppendToStream(AStream, INDENT2 +
|
|
'</Style>' + LF);
|
|
end;
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteStyles(AStream: TStream);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
AppendToStream(AStream, INDENT1 +
|
|
'<Styles>' + LF);
|
|
for i:=0 to (FWorkbook as TsWorkbook).GetNumCellFormats-1 do
|
|
WriteStyle(AStream, i);
|
|
AppendToStream(AStream, INDENT1 +
|
|
'</Styles>' + LF);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteTable(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
AppendToStream(AStream, TABLE_INDENT + Format(
|
|
'<Table ss:ExpandedColumnCount="%d" ss:ExpandedRowCount="%d" ' +
|
|
'x:FullColumns="1" x:FullRows="1" ' +
|
|
'ss:DefaultColumnWidth="%.2f" ' +
|
|
'ss:DefaultRowHeight="%.2f">' + LF,
|
|
[
|
|
FLastCol + 1, FLastRow + 1,
|
|
sheet.ReadDefaultColWidth(suPoints),
|
|
sheet.ReadDefaultRowHeight(suPoints)
|
|
],
|
|
FPointSeparatorSettings
|
|
));
|
|
|
|
WriteColumns(AStream, AWorksheet);
|
|
WriteRows(AStream, AWorksheet);
|
|
|
|
AppendToStream(AStream, TABLE_INDENT +
|
|
'</Table>' + LF);
|
|
end;
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
Writes an ExcelXML document to a stream
|
|
-------------------------------------------------------------------------------}
|
|
procedure TsSpreadExcelXMLWriter.WriteToStream(AStream: TStream;
|
|
AParams: TsStreamParams = []);
|
|
begin
|
|
Unused(AParams);
|
|
|
|
AppendToStream(AStream,
|
|
'<?xml version="1.0"?>' + LF +
|
|
'<?mso-application progid="Excel.Sheet"?>' + LF
|
|
);
|
|
AppendToStream(AStream,
|
|
'<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"' + LF +
|
|
' xmlns:o="urn:schemas-microsoft-com:office:office"' + LF +
|
|
' xmlns:x="urn:schemas-microsoft-com:office:excel"' + LF +
|
|
' xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"' + LF +
|
|
' xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"' + LF +
|
|
' xmlns:html="http://www.w3.org/TR/REC-html40">' + LF);
|
|
|
|
WriteDocumentProperties(AStream);
|
|
WriteCustomDocumentProperties(AStream);
|
|
WriteOfficeDocumentSettings(AStream);
|
|
WriteExcelWorkbook(AStream);
|
|
WriteStyles(AStream);
|
|
WriteWorksheets(AStream);
|
|
|
|
AppendToStream(AStream,
|
|
'</Workbook>');
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteWorksheet(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
protectedStr: String;
|
|
begin
|
|
FWorksheet := AWorksheet;
|
|
GetSheetDimensions(FWorksheet, FFirstRow, FLastRow, FFirstCol, FLastCol);
|
|
|
|
if FWorksheet.IsProtected then
|
|
protectedStr := ' ss:Protected="1"'
|
|
else
|
|
protectedStr := '';
|
|
|
|
AppendToStream(AStream, Format(
|
|
' <Worksheet ss:Name="%s"%s>' + LF, [
|
|
UTF8TextToXMLText(AWorksheet.Name),
|
|
protectedStr
|
|
]) );
|
|
WriteNames(AStream, AWorksheet);
|
|
WriteTable(AStream, AWorksheet);
|
|
WriteWorksheetOptions(AStream, AWorksheet);
|
|
WriteConditionalFormatting(AStream, AWorksheet);
|
|
WritePageBreaks(AStream, AWorksheet);
|
|
AppendToStream(AStream,
|
|
' </Worksheet>' + LF
|
|
);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteWorksheetOptions(AStream: TStream;
|
|
AWorksheet: TsBasicWorksheet);
|
|
var
|
|
footerStr, headerStr: String;
|
|
hideGridStr: String;
|
|
hideHeadersStr: String;
|
|
frozenStr: String;
|
|
layoutStr: String;
|
|
marginStr: String;
|
|
selectedStr: String;
|
|
protectStr: String;
|
|
visibleStr: String;
|
|
printStr: String;
|
|
fitToPageStr: String;
|
|
enableSelectionStr: String;
|
|
sheet: TsWorksheet absolute AWorksheet;
|
|
begin
|
|
// Orientation, some PageLayout.Options
|
|
layoutStr := GetLayoutStr(AWorksheet);
|
|
if layoutStr <> '' then layoutStr := INDENT4 + layoutStr + LF;
|
|
|
|
// Header
|
|
headerStr := GetPageHeaderStr(AWorksheet);
|
|
if headerStr <> '' then headerStr := INDENT4 + headerStr + LF;
|
|
|
|
// Footer
|
|
footerStr := GetPageFooterStr(AWorksheet);
|
|
if footerStr <> '' then footerStr := INDENT4 + footerStr + LF;
|
|
|
|
// Page margins
|
|
marginStr := GetPageMarginStr(AWorksheet);
|
|
if marginStr <> '' then marginStr := INDENT4 + marginStr + LF;
|
|
|
|
// Show/hide grid lines
|
|
if not (soShowGridLines in AWorksheet.Options) then
|
|
hideGridStr := INDENT3 + '<DoNotDisplayGridlines/>' + LF
|
|
else
|
|
hideGridStr := '';
|
|
|
|
// Show/hide column/row headers
|
|
if not (soShowHeaders in AWorksheet.Options) then
|
|
hideHeadersStr := INDENT3 + '<DoNotDisplayHeadings/>' + LF
|
|
else
|
|
hideHeadersStr := '';
|
|
|
|
if (FWorkbook as TsWorkbook).ActiveWorksheet = AWorksheet then
|
|
selectedStr := INDENT3 + '<Selected/>' + LF
|
|
else
|
|
selectedStr := '';
|
|
|
|
// FitToPage node
|
|
if poFitPages in sheet.PageLayout.Options then
|
|
fitToPageStr := INDENT3 + '<FitToPage/>' + LF
|
|
else
|
|
fitToPageStr := '';
|
|
|
|
// Print node
|
|
printStr := GetPrintStr(AWorksheet);
|
|
|
|
// Visible
|
|
if (soHidden in AWorksheet.Options) then
|
|
visibleStr := INDENT3 + '<Visible>SheetHidden</Visible>' + LF
|
|
else
|
|
visibleStr := '';
|
|
|
|
// Frozen panes
|
|
frozenStr := GetFrozenPanesStr(AWorksheet, INDENT3);
|
|
|
|
// Protection
|
|
protectStr := Format(INDENT3 + '<ProtectObjects>%s</ProtectObjects>' + LF +
|
|
INDENT3 + '<ProtectScenarios>%s</ProtectScenarios>' + LF, [
|
|
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 + '<EnableSelection>' + LF;
|
|
if spSelectUnlockedCells in sheet.Protection then
|
|
enableSelectionStr := enableSelectionStr + INDENT4 + '<NoSelection/>' + LF;
|
|
if (sheet.Protection * [spSelectLockedCells, spSelectUnlockedCells] = [spSelectLockedCells]) then
|
|
enableSelectionStr := enableSelectionStr + INDENT4 + '<Unlocked/>' + LF;
|
|
enableSelectionStr := INDENT3 + '</EnableSelection>' + LF;
|
|
end;
|
|
|
|
// todo - Several protection options
|
|
|
|
// Put it all together...
|
|
AppendToStream(AStream, INDENT2 +
|
|
'<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">' + LF + INDENT3 +
|
|
'<PageSetup>' + LF +
|
|
layoutStr +
|
|
headerStr +
|
|
footerStr +
|
|
marginStr + INDENT3 +
|
|
'</PageSetup>' + LF +
|
|
fitToPageStr + INDENT3 +
|
|
'<Print>' + LF +
|
|
printStr + LF + INDENT3 +
|
|
'</Print>' + LF +
|
|
visibleStr +
|
|
selectedStr +
|
|
IfThen(not (spFormatCells in sheet.Protection), INDENT4 + '<AllowFormatCells/>' + LF) +
|
|
IfThen(not (spFormatColumns in sheet.Protection), INDENT4 + '<AllowSizeCols/>' + LF) +
|
|
IfThen(not (spFormatRows in sheet.Protection), INDENT4 + '<AllowSizeRows/>' + LF) +
|
|
IfThen(not (spDeleteColumns in sheet.Protection), INDENT4 + '<AllowDeleteCols/>' + LF) +
|
|
IfThen(not (spDeleteRows in sheet.Protection), INDENT4 + '<AllowDeleteRows/>' + LF) +
|
|
IfThen(not (spInsertColumns in sheet.Protection), INDENT4 + '<AllowInsertCols/>' + LF) +
|
|
IfThen(not (spInsertHyperlinks in sheet.Protection), INDENT4 + '<AllowInsertHyperlinks/>' + LF) +
|
|
IfThen(not (spInsertRows in sheet.Protection), INDENT4 + '<AllowInsertRows/>' + LF) +
|
|
IfThen(not (spSort in sheet.Protection), INDENT4 + '<AllowSort/>' + LF) +
|
|
enableSelectionStr +
|
|
protectStr +
|
|
frozenStr +
|
|
hideGridStr +
|
|
hideHeadersStr + INDENT2 +
|
|
'</WorksheetOptions>' + LF
|
|
);
|
|
end;
|
|
|
|
procedure TsSpreadExcelXMLWriter.WriteWorksheets(AStream: TStream);
|
|
var
|
|
i: Integer;
|
|
book: TsWorkbook;
|
|
begin
|
|
book := FWorkbook as TsWorkbook;
|
|
for i:=0 to book.GetWorksheetCount-1 do
|
|
WriteWorksheet(AStream, book.GetWorksheetByIndex(i));
|
|
end;
|
|
|
|
|
|
initialization
|
|
|
|
// Registers this reader / writer in fpSpreadsheet
|
|
sfidExcelXML := RegisterSpreadFormat(sfExcelXML,
|
|
TsSpreadExcelXMLReader, TsSpreadExcelXMLWriter,
|
|
STR_FILEFORMAT_EXCEL_XML, 'ExcelXML', [STR_XML_EXCEL_EXTENSION]
|
|
);
|
|
|
|
end.
|