2008-02-24 13:18:34 +00:00
|
|
|
{
|
|
|
|
xlsxooxml.pas
|
|
|
|
|
|
|
|
Writes an OOXML (Office Open XML) document
|
|
|
|
|
|
|
|
An OOXML document is a compressed ZIP file with the following files inside:
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
[Content_Types].xml -
|
2014-07-08 22:02:13 +00:00
|
|
|
_rels/.rels -
|
|
|
|
xl/_rels\workbook.xml.rels -
|
|
|
|
xl/workbook.xml - Global workbook data and list of worksheets
|
|
|
|
xl/styles.xml -
|
|
|
|
xl/sharedStrings.xml -
|
|
|
|
xl/worksheets\sheet1.xml - Contents of each worksheet
|
2008-02-24 13:18:34 +00:00
|
|
|
...
|
2014-07-08 22:02:13 +00:00
|
|
|
xl/worksheets\sheetN.xml
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
Specifications obtained from:
|
|
|
|
|
|
|
|
http://openxmldeveloper.org/default.aspx
|
|
|
|
|
|
|
|
AUTHORS: Felipe Monteiro de Carvalho
|
|
|
|
}
|
|
|
|
unit xlsxooxml;
|
|
|
|
|
|
|
|
{$ifdef fpc}
|
|
|
|
{$mode delphi}
|
|
|
|
{$endif}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2009-02-02 09:58:51 +00:00
|
|
|
Classes, SysUtils,
|
2014-07-05 18:11:41 +00:00
|
|
|
{$IF FPC_FULLVERSION >= 20701}
|
2013-12-07 13:42:22 +00:00
|
|
|
zipper,
|
|
|
|
{$ELSE}
|
|
|
|
fpszipper,
|
|
|
|
{$ENDIF}
|
2011-08-29 10:55:22 +00:00
|
|
|
{xmlread, DOM,} AVL_Tree,
|
2013-12-23 12:11:20 +00:00
|
|
|
fpspreadsheet, fpsutils;
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
|
2014-05-15 12:53:56 +00:00
|
|
|
{ TsOOXMLFormatList }
|
|
|
|
TsOOXMLNumFormatList = class(TsCustomNumFormatList)
|
|
|
|
protected
|
|
|
|
{
|
|
|
|
procedure AddBuiltinFormats; override;
|
|
|
|
procedure Analyze(AFormatIndex: Integer; var AFormatString: String;
|
|
|
|
var ANumFormat: TsNumberFormat; var ADecimals: Word); override;
|
|
|
|
}
|
|
|
|
public
|
|
|
|
{
|
|
|
|
function FormatStringForWriting(AIndex: Integer): String; override;
|
|
|
|
}
|
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ TsSpreadOOXMLWriter }
|
|
|
|
|
|
|
|
TsSpreadOOXMLWriter = class(TsCustomSpreadWriter)
|
|
|
|
protected
|
2012-04-27 08:01:15 +00:00
|
|
|
FPointSeparatorSettings: TFormatSettings;
|
2009-02-02 09:58:51 +00:00
|
|
|
FSharedStringsCount: Integer;
|
2014-05-15 12:53:56 +00:00
|
|
|
protected
|
|
|
|
{ Helper routines }
|
|
|
|
procedure CreateNumFormatList; override;
|
2014-07-10 15:55:40 +00:00
|
|
|
procedure CreateStreams;
|
|
|
|
procedure DestroyStreams;
|
|
|
|
function GetStyleIndex(ACell: PCell): Cardinal;
|
2014-05-15 12:53:56 +00:00
|
|
|
protected
|
2009-02-02 09:58:51 +00:00
|
|
|
{ Streams with the contents of files }
|
2014-07-10 15:55:40 +00:00
|
|
|
FStreamClass: TsStreamClass;
|
|
|
|
FSContentTypes: TStream;
|
|
|
|
FSRelsRels: TStream;
|
|
|
|
FSWorkbook: TStream;
|
|
|
|
FSWorkbookRels: TStream;
|
|
|
|
FSStyles: TStream;
|
|
|
|
FSSharedStrings: TStream;
|
|
|
|
FSSharedStrings_complete: TStream;
|
|
|
|
FSSheets: array of TStream;
|
2011-08-29 10:55:22 +00:00
|
|
|
FCurSheetNum: Integer;
|
2014-05-15 12:53:56 +00:00
|
|
|
protected
|
2014-07-10 15:55:40 +00:00
|
|
|
{ Routines to write the files }
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure WriteGlobalFiles;
|
|
|
|
procedure WriteContent;
|
2009-02-02 09:58:51 +00:00
|
|
|
procedure WriteWorksheet(CurSheet: TsWorksheet);
|
2014-05-15 12:53:56 +00:00
|
|
|
protected
|
2014-04-21 11:30:22 +00:00
|
|
|
{ Record writing methods }
|
|
|
|
//todo: add WriteDate
|
|
|
|
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); override;
|
|
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override;
|
|
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell); override;
|
2014-05-15 12:53:56 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
public
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
2008-02-24 13:18:34 +00:00
|
|
|
{ General writing methods }
|
|
|
|
procedure WriteStringToFile(AFileName, AString: string);
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure WriteToFile(const AFileName: string; const AOverwriteExisting: Boolean = False); override;
|
|
|
|
procedure WriteToStream(AStream: TStream); override;
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
const
|
|
|
|
{ OOXML general XML constants }
|
|
|
|
XML_HEADER = '<?xml version="1.0" encoding="utf-8" ?>';
|
|
|
|
|
|
|
|
{ OOXML Directory structure constants }
|
2014-07-08 22:02:13 +00:00
|
|
|
// Note: directory separators are always / because the .xlsx is a zip file which
|
|
|
|
// requires / instead of \, even on Windows; see
|
|
|
|
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
|
|
|
// 4.4.17.1 All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
|
2008-02-24 13:18:34 +00:00
|
|
|
OOXML_PATH_TYPES = '[Content_Types].xml';
|
2014-07-08 19:03:58 +00:00
|
|
|
OOXML_PATH_RELS = '_rels/';
|
|
|
|
OOXML_PATH_RELS_RELS = '_rels/.rels';
|
|
|
|
OOXML_PATH_XL = 'xl/';
|
|
|
|
OOXML_PATH_XL_RELS = 'xl/_rels/';
|
|
|
|
OOXML_PATH_XL_RELS_RELS = 'xl/_rels/workbook.xml.rels';
|
|
|
|
OOXML_PATH_XL_WORKBOOK = 'xl/workbook.xml';
|
|
|
|
OOXML_PATH_XL_STYLES = 'xl/styles.xml';
|
|
|
|
OOXML_PATH_XL_STRINGS = 'xl/sharedStrings.xml';
|
|
|
|
OOXML_PATH_XL_WORKSHEETS = 'xl/worksheets/';
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ OOXML schemas constants }
|
|
|
|
SCHEMAS_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
|
|
|
SCHEMAS_RELS = 'http://schemas.openxmlformats.org/package/2006/relationships';
|
|
|
|
SCHEMAS_DOC_RELS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
|
|
|
|
SCHEMAS_DOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
|
|
|
|
SCHEMAS_WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
|
|
|
|
SCHEMAS_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
|
|
|
|
SCHEMAS_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
|
|
|
|
SCHEMAS_SPREADML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
|
|
|
|
|
|
|
{ OOXML mime types constants }
|
|
|
|
MIME_XML = 'application/xml';
|
|
|
|
MIME_RELS = 'application/vnd.openxmlformats-package.relationships+xml';
|
|
|
|
MIME_SPREADML = 'application/vnd.openxmlformats-officedocument.spreadsheetml';
|
|
|
|
MIME_SHEET = MIME_SPREADML + '.sheet.main+xml';
|
|
|
|
MIME_WORKSHEET = MIME_SPREADML + '.worksheet+xml';
|
|
|
|
MIME_STYLES = MIME_SPREADML + '.styles+xml';
|
|
|
|
MIME_STRINGS = MIME_SPREADML + '.sharedStrings+xml';
|
|
|
|
|
2014-05-15 12:53:56 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ TsSpreadOOXMLWriter }
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteGlobalFiles;
|
2011-08-29 13:24:16 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
{ --- Content Types --- }
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
XML_HEADER);
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Types xmlns="' + SCHEMAS_TYPES + '">');
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Override PartName="/_rels/.rels" ContentType="' + MIME_RELS + '" />');
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />');
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Override PartName="/xl/workbook.xml" ContentType="' + MIME_SHEET + '" />');
|
|
|
|
|
|
|
|
for i:=1 to Workbook.GetWorksheetCount do
|
|
|
|
AppendToStream(FSContentTypes, Format(
|
|
|
|
'<Override PartName="/xl/worksheets/sheet%d.xml" ContentType="%s" />',
|
|
|
|
[i, MIME_WORKSHEET]));
|
|
|
|
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Override PartName="/xl/styles.xml" ContentType="' + MIME_STYLES + '" />');
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'<Override PartName="/xl/sharedStrings.xml" ContentType="' + MIME_STRINGS + '" />');
|
|
|
|
AppendToStream(FSContentTypes,
|
|
|
|
'</Types>');
|
|
|
|
|
|
|
|
{ --- RelsRels --- }
|
|
|
|
AppendToStream(FSRelsRels,
|
|
|
|
XML_HEADER);
|
|
|
|
AppendToStream(FSRelsRels, Format(
|
|
|
|
'<Relationships xmlns="%s">', [SCHEMAS_RELS]));
|
|
|
|
AppendToStream(FSRelsRels, Format(
|
|
|
|
'<Relationship Type="%s" Target="xl/workbook.xml" Id="rId1" />', [SCHEMAS_DOCUMENT]));
|
|
|
|
AppendToStream(FSRelsRels,
|
|
|
|
'</Relationships>');
|
|
|
|
|
|
|
|
{ --- Styles --- }
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
XML_Header);
|
|
|
|
AppendToStream(FSStyles, Format(
|
|
|
|
'<styleSheet xmlns="%s">', [SCHEMAS_SPREADML]));
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<fonts count="2">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<font><sz val="10" /><name val="Arial" /></font>',
|
|
|
|
'<font><sz val="10" /><name val="Arial" /><b val="true"/></font>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</fonts>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<fills count="2">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<fill>',
|
|
|
|
'<patternFill patternType="none" />',
|
|
|
|
'</fill>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<fill>',
|
|
|
|
'<patternFill patternType="gray125" />',
|
|
|
|
'</fill>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</fills>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<borders count="1">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<border>',
|
|
|
|
'<left /><right /><top /><bottom /><diagonal />',
|
|
|
|
'</border>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</borders>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<cellStyleXfs count="2">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />',
|
|
|
|
'<xf numFmtId="0" fontId="1" fillId="0" borderId="0" />');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</cellStyleXfs>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<cellXfs count="2">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" />',
|
|
|
|
'<xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" />');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</cellXfs>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<cellStyles count="1">',
|
|
|
|
'<cellStyle name="Normal" xfId="0" builtinId="0" />',
|
|
|
|
'</cellStyles>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<dxfs count="0" />');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleLight16" />');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</styleSheet>');
|
2009-02-02 09:58:51 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteContent;
|
2009-02-02 09:58:51 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
{ --- WorkbookRels ---
|
2009-02-02 09:58:51 +00:00
|
|
|
{ Workbook relations - Mark relation to all sheets }
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSWorkbookRels,
|
|
|
|
XML_HEADER);
|
|
|
|
AppendToStream(FSWorkbookRels,
|
|
|
|
'<Relationships xmlns="' + SCHEMAS_RELS + '">');
|
|
|
|
AppendToStream(FSWorkbookRels,
|
|
|
|
'<Relationship Id="rId1" Type="' + SCHEMAS_STYLES + '" Target="styles.xml" />');
|
|
|
|
AppendToStream(FSWorkbookRels,
|
|
|
|
'<Relationship Id="rId2" Type="' + SCHEMAS_STRINGS + '" Target="sharedStrings.xml" />');
|
|
|
|
|
|
|
|
for i:=1 to Workbook.GetWorksheetCount do
|
|
|
|
AppendToStream(FSWorkbookRels, Format(
|
|
|
|
'<Relationship Type="%s" Target="worksheets/sheet%d.xml" Id="rId%d" />',
|
|
|
|
[SCHEMAS_WORKSHEET, i, i+2]));
|
|
|
|
|
|
|
|
AppendToStream(FSWOrkbookRels,
|
|
|
|
'</Relationships>');
|
|
|
|
|
|
|
|
{ --- Workbook --- }
|
|
|
|
{ Global workbook data - Mark all sheets }
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
XML_HEADER);
|
|
|
|
AppendToStream(FSWorkbook, Format(
|
|
|
|
'<workbook xmlns="%s" xmlns:r="%s">', [SCHEMAS_SPREADML, SCHEMAS_DOC_RELS]));
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'<fileVersion appName="fpspreadsheet" />');
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'<workbookPr defaultThemeVersion="124226" />');
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'<bookViews>',
|
|
|
|
'<workbookView xWindow="480" yWindow="90" windowWidth="15195" windowHeight="12525" />',
|
|
|
|
'</bookViews>');
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'<sheets>');
|
|
|
|
for i:=1 to Workbook.GetWorksheetCount do
|
|
|
|
AppendToStream(FSWorkbook, Format(
|
|
|
|
'<sheet name="Sheet%d" sheetId="%d" r:id="rId%d" />', [i, i, i+2]));
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'</sheets>');
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'<calcPr calcId="114210" />');
|
|
|
|
AppendToStream(FSWorkbook,
|
|
|
|
'</workbook>');
|
|
|
|
|
|
|
|
// Preparation for shared strings
|
2009-02-02 09:58:51 +00:00
|
|
|
FSharedStringsCount := 0;
|
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
// Write all worksheets which fills also the shared strings
|
2014-04-23 22:29:32 +00:00
|
|
|
for i := 0 to Workbook.GetWorksheetCount - 1 do
|
|
|
|
WriteWorksheet(Workbook.GetWorksheetByIndex(i));
|
2009-02-02 09:58:51 +00:00
|
|
|
|
|
|
|
// Finalization of the shared strings document
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSharedStrings_complete,
|
|
|
|
XML_HEADER, Format(
|
|
|
|
'<sst xmlns="%s" count="%d" uniqueCount="%d">', [SCHEMAS_SPREADML, FSharedStringsCount, FSharedStringsCount]
|
|
|
|
));
|
|
|
|
FSSharedStrings.Position := 0;
|
|
|
|
FSSharedStrings_complete.CopyFrom(FSSharedStrings, FSSharedStrings.Size);
|
|
|
|
AppendToStream(FSSharedStrings_complete,
|
|
|
|
'</sst>');
|
|
|
|
FSSharedStrings_complete.Position := 0;
|
2009-02-02 09:58:51 +00:00
|
|
|
end;
|
|
|
|
|
2011-08-29 10:55:22 +00:00
|
|
|
{
|
|
|
|
FSheets[CurStr] :=
|
|
|
|
XML_HEADER + LineEnding +
|
|
|
|
'<worksheet xmlns="' + SCHEMAS_SPREADML + '" xmlns:r="' + SCHEMAS_DOC_RELS + '">' + LineEnding +
|
|
|
|
' <sheetViews>' + LineEnding +
|
|
|
|
' <sheetView workbookViewId="0" />' + LineEnding +
|
|
|
|
' </sheetViews>' + LineEnding +
|
|
|
|
' <sheetData>' + LineEnding +
|
|
|
|
' <row r="1" spans="1:4">' + LineEnding +
|
|
|
|
' <c r="A1">' + LineEnding +
|
|
|
|
' <v>1</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="B1">' + LineEnding +
|
|
|
|
' <v>2</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="C1">' + LineEnding +
|
|
|
|
' <v>3</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="D1">' + LineEnding +
|
|
|
|
' <v>4</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' </row>' + LineEnding +
|
|
|
|
' <row r="2" spans="1:4">' + LineEnding +
|
|
|
|
' <c r="A2" t="s">' + LineEnding +
|
|
|
|
' <v>0</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="B2" t="s">' + LineEnding +
|
|
|
|
' <v>1</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="C2" t="s">' + LineEnding +
|
|
|
|
' <v>2</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' <c r="D2" t="s">' + LineEnding +
|
|
|
|
' <v>3</v>' + LineEnding +
|
|
|
|
' </c>' + LineEnding +
|
|
|
|
' </row>' + LineEnding +
|
|
|
|
' </sheetData>' + LineEnding +
|
|
|
|
'</worksheet>';
|
|
|
|
}
|
2009-02-02 09:58:51 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteWorksheet(CurSheet: TsWorksheet);
|
|
|
|
var
|
2014-07-10 15:55:40 +00:00
|
|
|
r, c: Cardinal;
|
2014-05-26 15:27:35 +00:00
|
|
|
LastColIndex: Cardinal;
|
2011-08-29 10:55:22 +00:00
|
|
|
LCell: TCell;
|
|
|
|
AVLNode: TAVLTreeNode;
|
|
|
|
CellPosText: string;
|
2014-07-10 15:55:40 +00:00
|
|
|
// S: String;
|
2009-02-02 09:58:51 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
FCurSheetNum := Length(FSSheets);
|
|
|
|
SetLength(FSSheets, FCurSheetNum + 1);
|
2011-08-29 10:55:22 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
FSSheets[FCurSheetNum] := FStreamClass.Create; // create the stream
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2011-08-29 10:55:22 +00:00
|
|
|
// Header
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
XML_HEADER);
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<worksheet xmlns="%s" xmlns:r="%s">', [SCHEMAS_SPREADML, SCHEMAS_DOC_RELS]));
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'<sheetViews>');
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'<sheetView workbookViewId="0" />');
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</sheetViews>');
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'<sheetData>');
|
2011-08-29 10:55:22 +00:00
|
|
|
|
|
|
|
// The cells need to be written in order, row by row, cell by cell
|
2014-07-10 15:55:40 +00:00
|
|
|
LastColIndex := CurSheet.GetLastColIndex;
|
|
|
|
for r := 0 to CurSheet.GetLastRowIndex do begin
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<row r="%d" spans="1:%d">', [r+1, LastColIndex+1]));
|
|
|
|
// Write cells belonging to this row.
|
|
|
|
for c := 0 to LastColIndex do
|
2011-08-29 10:55:22 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
LCell.Row := r;
|
|
|
|
LCell.Col := c;
|
2011-08-29 10:55:22 +00:00
|
|
|
AVLNode := CurSheet.Cells.Find(@LCell);
|
|
|
|
if Assigned(AVLNode) then
|
|
|
|
WriteCellCallback(PCell(AVLNode.Data), nil)
|
|
|
|
else
|
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
CellPosText := CurSheet.CellPosToText(r, c);
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<c r="%s">', [CellPosText]),
|
|
|
|
'<v></v>',
|
|
|
|
'</c>');
|
2011-08-29 10:55:22 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</row>');
|
2011-08-29 10:55:22 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// Footer
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</sheetData>',
|
|
|
|
'</worksheet>');
|
2009-01-28 22:36:41 +00:00
|
|
|
end;
|
|
|
|
|
2011-09-01 07:55:12 +00:00
|
|
|
// This is an index to the section cellXfs from the styles.xml file
|
|
|
|
function TsSpreadOOXMLWriter.GetStyleIndex(ACell: PCell): Cardinal;
|
|
|
|
begin
|
|
|
|
if uffBold in ACell^.UsedFormattingFields then Result := 1
|
|
|
|
else Result := 0;
|
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
constructor TsSpreadOOXMLWriter.Create(AWorkbook: TsWorkbook);
|
2012-04-27 08:01:15 +00:00
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
inherited Create(AWorkbook);
|
2014-07-10 15:55:40 +00:00
|
|
|
FStreamClass := TMemoryStream;
|
2012-04-27 08:01:15 +00:00
|
|
|
|
|
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
|
|
|
end;
|
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.CreateNumFormatList;
|
2009-02-02 09:58:51 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
FreeAndNil(FNumFormatList);
|
|
|
|
FNumFormatList := TsOOXMLNumFormatList.Create(Workbook);
|
|
|
|
end;
|
2009-02-02 09:58:51 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
{ Creates the streams for the individual data files. Will be zipped into a
|
|
|
|
single xlsx file.
|
|
|
|
We use the variable FStreamClass here to be able to easily switch from a
|
|
|
|
memory stream to a file stream for very big files. }
|
|
|
|
procedure TsSpreadOOXMLWriter.CreateStreams;
|
|
|
|
begin
|
|
|
|
FSContentTypes := FStreamClass.Create;
|
|
|
|
FSRelsRels := FStreamClass.Create;
|
|
|
|
FSWorkbookRels := FStreamClass.Create;
|
|
|
|
FSWorkbook := FStreamClass.Create;
|
|
|
|
FSStyles := FStreamClass.Create;
|
|
|
|
FSSharedStrings := FStreamClass.Create;
|
|
|
|
FSSharedStrings_complete := FStreamClass.Create;
|
|
|
|
// FSSheets will be created when needed.
|
2009-02-02 09:58:51 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
{ Destroys the streams that were created by the writer }
|
|
|
|
procedure TsSpreadOOXMLWriter.DestroyStreams;
|
|
|
|
var
|
|
|
|
i: Integer;
|
2014-05-15 12:53:56 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
FSContentTypes.Free;
|
|
|
|
FSRelsRels.Free;
|
|
|
|
FSWorkbookRels.Free;
|
|
|
|
FSWorkbook.Free;
|
|
|
|
FSStyles.Free;
|
|
|
|
FSSharedStrings.Free;
|
|
|
|
FSSharedStrings_complete.Free;
|
|
|
|
|
|
|
|
for i := 0 to Length(FSSheets) - 1 do
|
|
|
|
FSSheets[i].Free;
|
|
|
|
SetLength(FSSheets, 0);
|
2014-05-15 12:53:56 +00:00
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes a string to a file. Helper convenience method.
|
|
|
|
}
|
2009-01-28 22:36:41 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteStringToFile(AFileName, AString: string);
|
|
|
|
var
|
|
|
|
TheStream : TFileStream;
|
|
|
|
S : String;
|
|
|
|
begin
|
|
|
|
TheStream := TFileStream.Create(AFileName, fmCreate);
|
|
|
|
S:=AString;
|
|
|
|
TheStream.WriteBuffer(Pointer(S)^,Length(S));
|
|
|
|
TheStream.Free;
|
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes an OOXML document to the disc
|
|
|
|
}
|
2009-11-08 19:21:23 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteToFile(const AFileName: string;
|
2014-04-23 22:29:32 +00:00
|
|
|
const AOverwriteExisting: Boolean);
|
2009-01-28 22:36:41 +00:00
|
|
|
var
|
2011-08-29 15:10:10 +00:00
|
|
|
lStream: TFileStream;
|
2014-06-20 15:58:22 +00:00
|
|
|
lMode: word;
|
2009-01-28 22:36:41 +00:00
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
if AOverwriteExisting
|
|
|
|
then lMode := fmCreate or fmOpenWrite
|
|
|
|
else lMode := fmCreate;
|
|
|
|
|
|
|
|
lStream:=TFileStream.Create(AFileName, lMode);
|
2009-02-02 09:58:51 +00:00
|
|
|
try
|
2014-04-23 22:29:32 +00:00
|
|
|
WriteToStream(lStream);
|
2009-02-02 09:58:51 +00:00
|
|
|
finally
|
2011-08-29 15:10:10 +00:00
|
|
|
FreeAndNil(lStream);
|
2009-02-02 09:58:51 +00:00
|
|
|
end;
|
2009-01-28 22:36:41 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteToStream(AStream: TStream);
|
2011-08-29 11:59:47 +00:00
|
|
|
var
|
|
|
|
FZip: TZipper;
|
|
|
|
i: Integer;
|
2009-01-28 22:36:41 +00:00
|
|
|
begin
|
2014-07-10 15:55:40 +00:00
|
|
|
{ Create the streams that will hold the file contents }
|
|
|
|
CreateStreams;
|
2011-08-29 11:59:47 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
{ Fill the streams with the contents of the files }
|
2014-04-23 22:29:32 +00:00
|
|
|
WriteGlobalFiles;
|
|
|
|
WriteContent;
|
2011-08-29 11:59:47 +00:00
|
|
|
|
|
|
|
{ Now compress the files }
|
|
|
|
FZip := TZipper.Create;
|
|
|
|
try
|
|
|
|
FZip.Entries.AddFileEntry(FSContentTypes, OOXML_PATH_TYPES);
|
|
|
|
FZip.Entries.AddFileEntry(FSRelsRels, OOXML_PATH_RELS_RELS);
|
|
|
|
FZip.Entries.AddFileEntry(FSWorkbookRels, OOXML_PATH_XL_RELS_RELS);
|
|
|
|
FZip.Entries.AddFileEntry(FSWorkbook, OOXML_PATH_XL_WORKBOOK);
|
|
|
|
FZip.Entries.AddFileEntry(FSStyles, OOXML_PATH_XL_STYLES);
|
2014-07-10 15:55:40 +00:00
|
|
|
FZip.Entries.AddFileEntry(FSSharedStrings_complete, OOXML_PATH_XL_STRINGS);
|
2011-08-29 11:59:47 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
for i := 0 to Length(FSSheets) - 1 do begin
|
|
|
|
FSSheets[i].Position:= 0;
|
2011-08-29 11:59:47 +00:00
|
|
|
FZip.Entries.AddFileEntry(FSSheets[i], OOXML_PATH_XL_WORKSHEETS + 'sheet' + IntToStr(i + 1) + '.xml');
|
2014-07-10 15:55:40 +00:00
|
|
|
end;
|
2011-08-29 11:59:47 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
// Stream position must be at beginning, it was moved to end during adding of xml strings.
|
|
|
|
FSContentTypes.Position := 0;
|
|
|
|
FSRelsRels.Position := 0;
|
|
|
|
FSWorkbookRels.Position := 0;
|
|
|
|
FSWorkbook.Position := 0;
|
|
|
|
FSStyles.Position := 0;
|
|
|
|
FSSharedStrings_complete.Position := 0;
|
2011-08-29 11:59:47 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
FZip.SaveToStream(AStream);
|
2011-08-29 11:59:47 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
finally
|
|
|
|
DestroyStreams;
|
2011-08-29 11:59:47 +00:00
|
|
|
FZip.Free;
|
|
|
|
end;
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadOOXMLWriter.WriteLabel ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes a string to the sheet
|
|
|
|
* If the string length exceeds 32767 bytes, the string
|
|
|
|
* will be truncated and an exception will be raised as
|
|
|
|
* a warning.
|
|
|
|
*
|
|
|
|
*******************************************************************}
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteLabel(AStream: TStream; const ARow,
|
2013-12-07 13:42:22 +00:00
|
|
|
ACol: Cardinal; const AValue: string; ACell: PCell);
|
|
|
|
const
|
|
|
|
MaxBytes=32767; //limit for this format
|
2011-08-29 10:55:22 +00:00
|
|
|
var
|
|
|
|
CellPosText: string;
|
2011-09-01 07:55:12 +00:00
|
|
|
lStyleIndex: Cardinal;
|
2013-12-07 13:42:22 +00:00
|
|
|
TextTooLong: boolean=false;
|
|
|
|
ResultingValue: string;
|
2014-07-10 15:55:40 +00:00
|
|
|
//S: string;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(AStream);
|
|
|
|
Unused(ARow, ACol, ACell);
|
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
// Office 2007-2010 (at least) support no more characters in a cell;
|
|
|
|
if Length(AValue)>MaxBytes then
|
|
|
|
begin
|
|
|
|
TextTooLong:=true;
|
|
|
|
ResultingValue:=Copy(AValue,1,MaxBytes); //may chop off multicodepoint UTF8 characters but well...
|
|
|
|
end
|
|
|
|
else
|
|
|
|
ResultingValue:=AValue;
|
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSharedStrings,
|
|
|
|
'<si>', Format(
|
|
|
|
'<t>%s</t>', [UTF8TextToXMLText(ResultingValue)]),
|
|
|
|
'</si>' );
|
2011-08-29 13:24:16 +00:00
|
|
|
|
2011-08-29 10:55:22 +00:00
|
|
|
CellPosText := TsWorksheet.CellPosToText(ARow, ACol);
|
2011-09-01 07:55:12 +00:00
|
|
|
lStyleIndex := GetStyleIndex(ACell);
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<c r="%s" s="%d" t="s"><v>%d</v></c>', [CellPosText, lStyleIndex, FSharedStringsCount]));
|
2011-08-29 15:06:41 +00:00
|
|
|
Inc(FSharedStringsCount);
|
2014-07-10 15:55:40 +00:00
|
|
|
|
2013-12-07 13:42:22 +00:00
|
|
|
{
|
|
|
|
//todo: keep a log of errors and show with an exception after writing file or something.
|
|
|
|
We can't just do the following
|
|
|
|
|
|
|
|
if TextTooLong then
|
|
|
|
Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]);
|
|
|
|
because the file wouldn't be written.
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes a number (64-bit IEE 754 floating point) to the sheet
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteNumber(AStream: TStream; const ARow,
|
2010-12-08 10:24:15 +00:00
|
|
|
ACol: Cardinal; const AValue: double; ACell: PCell);
|
2011-08-29 10:55:22 +00:00
|
|
|
var
|
|
|
|
CellPosText: String;
|
2012-04-27 08:01:15 +00:00
|
|
|
CellValueText: String;
|
2014-07-10 15:55:40 +00:00
|
|
|
//S: String;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(AStream, ACell);
|
2011-08-29 10:55:22 +00:00
|
|
|
CellPosText := TsWorksheet.CellPosToText(ARow, ACol);
|
2012-04-27 08:01:15 +00:00
|
|
|
CellValueText := Format('%g', [AValue], FPointSeparatorSettings);
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<c r="%s" s="0" t="n"><v>%s</v></c>', [CellPosText, CellValueText]));
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2013-12-23 12:11:20 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadOOXMLWriter.WriteDateTime ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes a date/time value as a text
|
|
|
|
* ISO 8601 format is used to preserve interoperability
|
|
|
|
* between locales.
|
|
|
|
*
|
|
|
|
* Note: this should be replaced by writing actual date/time values
|
|
|
|
*
|
|
|
|
*******************************************************************}
|
2013-12-22 14:02:04 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteDateTime(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
|
|
|
begin
|
2013-12-23 12:11:20 +00:00
|
|
|
WriteLabel(AStream, ARow, ACol, FormatDateTime(ISO8601Format, AValue), ACell);
|
2013-12-22 14:02:04 +00:00
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Registers this reader / writer on fpSpreadsheet
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
initialization
|
|
|
|
|
|
|
|
RegisterSpreadFormat(TsCustomSpreadReader, TsSpreadOOXMLWriter, sfOOXML);
|
|
|
|
|
|
|
|
end.
|
|
|
|
|