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)
|
2014-07-11 13:20:14 +00:00
|
|
|
private
|
2008-02-24 13:18:34 +00:00
|
|
|
protected
|
2012-04-27 08:01:15 +00:00
|
|
|
FPointSeparatorSettings: TFormatSettings;
|
2009-02-02 09:58:51 +00:00
|
|
|
FSharedStringsCount: Integer;
|
2014-07-13 22:09:27 +00:00
|
|
|
FFillList: array of PCell;
|
|
|
|
FBorderList: array of PCell;
|
2014-05-15 12:53:56 +00:00
|
|
|
protected
|
|
|
|
{ Helper routines }
|
2014-07-12 22:12:38 +00:00
|
|
|
procedure AddDefaultFormats; override;
|
2014-05-15 12:53:56 +00:00
|
|
|
procedure CreateNumFormatList; override;
|
2014-07-10 15:55:40 +00:00
|
|
|
procedure CreateStreams;
|
|
|
|
procedure DestroyStreams;
|
2014-07-13 22:09:27 +00:00
|
|
|
function FindBorderInList(ACell: PCell): Integer;
|
|
|
|
function FindFillInList(ACell: PCell): Integer;
|
2014-07-10 15:55:40 +00:00
|
|
|
function GetStyleIndex(ACell: PCell): Cardinal;
|
2014-07-13 22:09:27 +00:00
|
|
|
procedure ListAllBorders;
|
|
|
|
procedure ListAllFills;
|
2014-07-12 22:12:38 +00:00
|
|
|
procedure ResetStreams;
|
2014-07-13 22:09:27 +00:00
|
|
|
procedure WriteBorderList(AStream: TStream);
|
|
|
|
procedure WriteFillList(AStream: TStream);
|
2014-07-12 22:12:38 +00:00
|
|
|
procedure WriteFontList(AStream: TStream);
|
|
|
|
procedure WriteStyleList(AStream: TStream; ANodeName: String);
|
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
|
|
|
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
|
2014-07-11 20:00:49 +00:00
|
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); override;
|
2014-04-21 11:30:22 +00:00
|
|
|
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
|
|
|
|
|
2014-07-10 20:43:46 +00:00
|
|
|
uses
|
|
|
|
variants;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
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-07-12 22:12:38 +00:00
|
|
|
{ Adds built-in styles:
|
|
|
|
- Default style for cells having no specific formatting
|
|
|
|
- Bold styles for cells having UsedFormattingFileds = [uffBold]
|
|
|
|
All other styles will be added by "ListAllFormattingStyles".
|
|
|
|
}
|
|
|
|
procedure TsSpreadOOXMLWriter.AddDefaultFormats();
|
|
|
|
// We store the index of the XF record that will be assigned to this style in
|
|
|
|
// the "row" of the style. Will be needed when writing the XF record.
|
|
|
|
// --- This is needed for BIFF. Not clear if it is important here as well...
|
|
|
|
var
|
|
|
|
len: Integer;
|
|
|
|
begin
|
|
|
|
SetLength(FFormattingStyles, 2);
|
|
|
|
|
|
|
|
// Default style
|
|
|
|
FillChar(FFormattingStyles[0], SizeOf(TCell), 0);
|
|
|
|
FFormattingStyles[0].BorderStyles := DEFAULT_BORDERSTYLES;
|
|
|
|
FFormattingStyles[0].Row := 0;
|
|
|
|
|
|
|
|
// Bold style
|
|
|
|
FillChar(FFormattingStyles[1], SizeOf(TCell), 0);
|
|
|
|
FFormattingStyles[1].UsedFormattingFields := [uffBold];
|
|
|
|
FFormattingStyles[1].FontIndex := 1; // this is the "bold" font
|
|
|
|
FFormattingStyles[1].Row := 1;
|
|
|
|
|
|
|
|
NextXFIndex := 2;
|
|
|
|
end;
|
|
|
|
|
2014-07-13 22:09:27 +00:00
|
|
|
{ Looks for the combination of border attributes of the given cell in the
|
|
|
|
FBorderList and returns its index. }
|
|
|
|
function TsSpreadOOXMLWriter.FindBorderInList(ACell: PCell): Integer;
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
styleCell: PCell;
|
|
|
|
begin
|
|
|
|
// No cell, or border-less --> index 0
|
|
|
|
if (ACell = nil) or not (uffBorder in ACell^.UsedFormattingFields) then begin
|
|
|
|
Result := 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
for i:=0 to High(FBorderList) do begin
|
|
|
|
styleCell := FBorderList[i];
|
|
|
|
if SameCellBorders(styleCell, ACell) then begin
|
|
|
|
Result := i;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Not found --> return -1
|
|
|
|
Result := -1;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Looks for the combination of fill attributes of the given cell in the
|
|
|
|
FFillList and returns its index. }
|
|
|
|
function TsSpreadOOXMLWriter.FindFillInList(ACell: PCell): Integer;
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
styleCell: PCell;
|
|
|
|
begin
|
|
|
|
if (ACell = nil) or not (uffBackgroundColor in ACell^.UsedFormattingFields)
|
|
|
|
then begin
|
|
|
|
Result := 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Index 0 is "no fill" which already has been handled.
|
|
|
|
for i:=2 to High(FFillList) do begin
|
|
|
|
styleCell := FFillList[i];
|
|
|
|
if (uffBackgroundColor in styleCell^.UsedFormattingFields) then
|
|
|
|
if (styleCell^.BackgroundColor = ACell^.BackgroundColor) then begin
|
|
|
|
Result := i;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Not found --> return -1
|
|
|
|
Result := -1;
|
|
|
|
end;
|
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
{ Determines the formatting index which a given cell has in list of
|
|
|
|
"FormattingStyles" which correspond to the section cellXfs of the styles.xml
|
|
|
|
file. }
|
|
|
|
function TsSpreadOOXMLWriter.GetStyleIndex(ACell: PCell): Cardinal;
|
|
|
|
begin
|
|
|
|
Result := FindFormattingInList(ACell);
|
|
|
|
if Result = -1 then
|
|
|
|
Result := 0;
|
|
|
|
end;
|
|
|
|
|
2014-07-13 22:09:27 +00:00
|
|
|
{ Creates a list of all border styles found in the workbook.
|
|
|
|
The list contains indexes into the array FFormattingStyles for each unique
|
|
|
|
combination of border attributes.
|
|
|
|
To be used for the styles.xml. }
|
|
|
|
procedure TsSpreadOOXMLWriter.ListAllBorders;
|
|
|
|
var
|
|
|
|
styleCell: PCell;
|
|
|
|
i, n : Integer;
|
|
|
|
begin
|
|
|
|
// first list entry is a no-border cell
|
|
|
|
SetLength(FBorderList, 1);
|
|
|
|
FBorderList[0] := nil;
|
|
|
|
|
|
|
|
n := 1;
|
|
|
|
for i := 0 to High(FFormattingStyles) do begin
|
|
|
|
styleCell := @FFormattingStyles[i];
|
|
|
|
if FindBorderInList(styleCell) = -1 then begin
|
|
|
|
SetLength(FBorderList, n+1);
|
|
|
|
FBorderList[n] := styleCell;
|
|
|
|
inc(n);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Creates a list of all fill styles found in the workbook.
|
|
|
|
The list contains indexes into the array FFormattingStyles for each unique
|
|
|
|
combination of fill attributes.
|
|
|
|
Currently considers only backgroundcolor, fill style is always "solid".
|
|
|
|
To be used for styles.xml. }
|
|
|
|
procedure TsSpreadOOXMLWriter.ListAllFills;
|
|
|
|
var
|
|
|
|
styleCell: PCell;
|
|
|
|
i, n: Integer;
|
|
|
|
begin
|
|
|
|
// Add built-in fills first.
|
|
|
|
SetLength(FFillList, 2);
|
|
|
|
FFillList[0] := nil; // built-in "no fill"
|
|
|
|
FFillList[1] := nil; // built-in "gray125"
|
|
|
|
|
|
|
|
n := 2;
|
|
|
|
for i := 0 to High(FFormattingStyles) do begin
|
|
|
|
styleCell := @FFormattingStyles[i];
|
|
|
|
if FindFillInList(styleCell) = -1 then begin
|
|
|
|
SetLength(FFillList, n+1);
|
|
|
|
FFillList[n] := styleCell;
|
|
|
|
inc(n);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadOOXMLWriter.WriteBorderList(AStream: TStream);
|
|
|
|
|
|
|
|
procedure WriteBorderStyle(AStream: TStream; ACell: PCell; ABorder: TsCellBorder);
|
|
|
|
{ border names found in xlsx files for Excel selections:
|
|
|
|
"thin", "hair", "dotted", "dashed", "dashDotDot", "dashDot", "mediumDashDotDot",
|
|
|
|
"slantDashDot", "mediumDashDot", "mediumDashed", "medium", "thick", "double" }
|
|
|
|
var
|
|
|
|
borderName: String;
|
|
|
|
styleName: String;
|
|
|
|
colorName: String;
|
|
|
|
rgb: TsColorValue;
|
|
|
|
begin
|
|
|
|
// Border line location
|
|
|
|
case ABorder of
|
|
|
|
cbWest : borderName := 'left';
|
|
|
|
cbEast : borderName := 'right';
|
|
|
|
cbNorth : borderName := 'top';
|
|
|
|
cbSouth : borderName := 'bottom';
|
|
|
|
end;
|
|
|
|
if (ABorder in ACell^.Border) then begin
|
|
|
|
// Line style
|
|
|
|
case ACell.BorderStyles[ABorder].LineStyle of
|
|
|
|
lsThin : styleName := 'thin';
|
|
|
|
lsMedium : styleName := 'medium';
|
|
|
|
lsDashed : styleName := 'dashed';
|
|
|
|
lsDotted : styleName := 'dotted';
|
|
|
|
lsThick : styleName := 'thick';
|
|
|
|
lsDouble : styleName := 'double';
|
|
|
|
lsHair : styleName := 'hair';
|
|
|
|
else raise Exception.Create('TsOOXMLWriter.WriteBorderList: LineStyle not supported.');
|
|
|
|
end;
|
|
|
|
// Border color
|
|
|
|
rgb := Workbook.GetPaletteColor(ACell^.BorderStyles[ABorder].Color);
|
|
|
|
colorName := Copy(ColorToHTMLColorStr(rgb), 2, 255);
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<%s style="%s"><color rgb="%s" /></%s>',
|
|
|
|
[borderName, styleName, colorName, borderName]
|
|
|
|
));
|
|
|
|
end else
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<%s />', [borderName]));
|
|
|
|
end;
|
|
|
|
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
styleCell: PCell;
|
|
|
|
begin
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<borders count="%d">', [Length(FBorderList)]));
|
|
|
|
|
|
|
|
// index 0 -- build-in "no borders"
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<border>',
|
|
|
|
'<left /><right /><top /><bottom /><diagonal />',
|
|
|
|
'</border>');
|
|
|
|
|
|
|
|
for i:=1 to High(FBorderList) do begin
|
|
|
|
styleCell := FBorderList[i];
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<border>');
|
|
|
|
WriteBorderStyle(AStream, styleCell, cbWest);
|
|
|
|
WriteBorderStyle(AStream, styleCell, cbEast);
|
|
|
|
WriteBorderStyle(AStream, styleCell, cbNorth);
|
|
|
|
WriteBorderStyle(AStream, styleCell, cbSouth);
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<diagonal />',
|
|
|
|
'</border>');
|
|
|
|
end;
|
|
|
|
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'</borders>');
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadOOXMLWriter.WriteFillList(AStream: TStream);
|
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
styleCell: PCell;
|
|
|
|
rgb: TsColorValue;
|
|
|
|
begin
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<fills count="%d">', [Length(FFillList)]));
|
|
|
|
|
|
|
|
// index 0 -- built-in empty fill
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<fill>',
|
|
|
|
'<patternFill patternType="none" />',
|
|
|
|
'</fill>');
|
|
|
|
|
|
|
|
// index 1 -- built-in gray125 pattern
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<fill>',
|
|
|
|
'<patternFill patternType="gray125" />',
|
|
|
|
'</fill>');
|
|
|
|
|
|
|
|
// user-defined fills
|
|
|
|
for i:=2 to High(FFillList) do begin
|
|
|
|
styleCell := FFillList[i];
|
|
|
|
rgb := Workbook.GetPaletteColor(styleCell^.BackgroundColor);
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<fill>',
|
|
|
|
'<patternFill patternType="solid">');
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<fgColor rgb="%s" />', [Copy(ColorToHTMLColorStr(rgb), 2, 255)]),
|
|
|
|
'<bgColor indexed="64" />');
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'</patternFill>',
|
|
|
|
'</fill>');
|
|
|
|
end;
|
|
|
|
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</fills>');
|
|
|
|
end;
|
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
{ Writes the fontlist of the workbook to the stream. The font id used in xf
|
|
|
|
records is given by the index of a font in the list. Therefore, we have
|
|
|
|
to write an empty record for font #4 which is nil due to compatibility with BIFF }
|
|
|
|
procedure TsSpreadOOXMLWriter.WriteFontList(AStream: TStream);
|
2014-07-11 22:43:00 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
font: TsFont;
|
2014-07-12 22:12:38 +00:00
|
|
|
s: String;
|
2014-07-11 22:43:00 +00:00
|
|
|
rgb: TsColorValue;
|
|
|
|
begin
|
|
|
|
AppendToStream(FSStyles, Format(
|
|
|
|
'<fonts count="%d">', [Workbook.GetFontCount]));
|
|
|
|
for i:=0 to Workbook.GetFontCount-1 do begin
|
|
|
|
font := Workbook.GetFont(i);
|
2014-07-12 22:12:38 +00:00
|
|
|
if font = nil then
|
|
|
|
AppendToStream(AStream, '<font />')
|
|
|
|
// Font #4 is missing in fpspreadsheet due to BIFF compatibility. We write
|
|
|
|
// an empty node to keep the numbers in sync with the stored font index.
|
|
|
|
else begin
|
|
|
|
s := Format('<sz val="%g" /><name val="%s" />', [font.Size, font.FontName]);
|
|
|
|
if (fssBold in font.Style) then
|
|
|
|
s := s + '<b />';
|
|
|
|
if (fssItalic in font.Style) then
|
|
|
|
s := s + '<i />';
|
|
|
|
if (fssUnderline in font.Style) then
|
|
|
|
s := s + '<u />';
|
|
|
|
if (fssStrikeout in font.Style) then
|
|
|
|
s := s + '<strike />';
|
2014-07-11 22:43:00 +00:00
|
|
|
if font.Color <> scBlack then begin
|
|
|
|
rgb := Workbook.GetPaletteColor(font.Color);
|
2014-07-12 22:12:38 +00:00
|
|
|
s := s + Format('<color rgb="%s" />', [Copy(ColorToHTMLColorStr(rgb), 2, 255)]);
|
|
|
|
end;
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'<font>', s, '</font>');
|
2014-07-11 22:43:00 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
AppendToStream(AStream,
|
|
|
|
'</fonts>');
|
|
|
|
end;
|
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
{ Writes the style list which the writer has collected in FFormattingStyles. }
|
|
|
|
procedure TsSpreadOOXMLWriter.WriteStyleList(AStream: TStream; ANodeName: String);
|
|
|
|
var
|
|
|
|
styleCell: TCell;
|
2014-07-13 15:23:07 +00:00
|
|
|
s, sAlign: String;
|
2014-07-12 22:12:38 +00:00
|
|
|
fontID: Integer;
|
|
|
|
numFmtId: Integer;
|
|
|
|
fillId: Integer;
|
|
|
|
borderId: Integer;
|
|
|
|
begin
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<%s count="%d">', [ANodeName, Length(FFormattingStyles)]));
|
|
|
|
|
|
|
|
for styleCell in FFormattingStyles do begin
|
|
|
|
s := '';
|
2014-07-13 15:23:07 +00:00
|
|
|
sAlign := '';
|
2014-07-12 22:12:38 +00:00
|
|
|
|
|
|
|
{ Number format }
|
|
|
|
numFmtId := 0;
|
|
|
|
s := s + Format('numFmtId="%d" ', [numFmtId]);
|
|
|
|
|
|
|
|
{ Font }
|
|
|
|
fontId := 0;
|
|
|
|
if (uffBold in styleCell.UsedFormattingFields) then
|
|
|
|
fontId := 1;
|
|
|
|
if (uffFont in styleCell.UsedFormattingFields) then
|
|
|
|
fontId := styleCell.FontIndex;
|
|
|
|
s := s + Format('fontId="%d" ', [fontId]);
|
|
|
|
if fontID > 0 then s := s + 'applyFont="1" ';
|
|
|
|
|
|
|
|
if ANodeName = 'cellXfs' then s := s + 'xfId="0" ';
|
|
|
|
|
2014-07-13 14:58:12 +00:00
|
|
|
{ Text rotation }
|
|
|
|
if (uffTextRotation in styleCell.UsedFormattingFields) or (styleCell.TextRotation <> trHorizontal)
|
2014-07-13 15:23:07 +00:00
|
|
|
then
|
2014-07-13 14:58:12 +00:00
|
|
|
case styleCell.TextRotation of
|
2014-07-13 15:23:07 +00:00
|
|
|
rt90DegreeClockwiseRotation : sAlign := sAlign + Format('textRotation="%d" ', [180]);
|
|
|
|
rt90DegreeCounterClockwiseRotation: sAlign := sAlign + Format('textRotation="%d" ', [90]);
|
|
|
|
rtStacked : sAlign := sAlign + Format('textRotation="%d" ', [255]);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Text alignment }
|
|
|
|
if (uffHorAlign in styleCell.UsedFormattingFields) or (styleCell.HorAlignment <> haDefault)
|
|
|
|
then
|
|
|
|
case styleCell.HorAlignment of
|
|
|
|
haLeft : sAlign := sAlign + 'horizontal="left" ';
|
|
|
|
haCenter: sAlign := sAlign + 'horizontal="center" ';
|
|
|
|
haRight : sAlign := sAlign + 'horizontal="right" ';
|
|
|
|
end;
|
|
|
|
|
|
|
|
if (uffVertAlign in styleCell.UsedformattingFields) or (styleCell.VertAlignment <> vaDefault)
|
|
|
|
then
|
|
|
|
case styleCell.VertAlignment of
|
|
|
|
vaTop : sAlign := sAlign + 'vertical="top" ';
|
|
|
|
vaCenter: sAlign := sAlign + 'vertical="center" ';
|
|
|
|
vaBottom: sAlign := sAlign + 'vertical="bottom" ';
|
2014-07-13 14:58:12 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
{ Fill }
|
2014-07-13 22:09:27 +00:00
|
|
|
fillID := FindFillInList(@styleCell);
|
|
|
|
if fillID = -1 then fillID := 0;
|
2014-07-12 22:12:38 +00:00
|
|
|
s := s + Format('fillId="%d" ', [fillID]);
|
|
|
|
|
|
|
|
{ Border }
|
2014-07-13 22:09:27 +00:00
|
|
|
borderID := FindBorderInList(@styleCell);
|
|
|
|
if borderID = -1 then borderID := 0;
|
2014-07-12 22:12:38 +00:00
|
|
|
s := s + Format('borderId="%d" ', [borderID]);
|
|
|
|
|
|
|
|
{ Write everything to stream }
|
2014-07-13 15:23:07 +00:00
|
|
|
if sAlign = '' then
|
2014-07-13 14:58:12 +00:00
|
|
|
AppendToStream(AStream,
|
|
|
|
'<xf ' + s + '/>')
|
|
|
|
else
|
|
|
|
AppendToStream(AStream,
|
2014-07-13 15:23:07 +00:00
|
|
|
'<xf ' + s + 'applyAlignment="1">',
|
|
|
|
'<alignment ' + sAlign + ' />',
|
2014-07-13 14:58:12 +00:00
|
|
|
'</xf>');
|
2014-07-12 22:12:38 +00:00
|
|
|
end;
|
2014-07-13 14:58:12 +00:00
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
AppendToStream(FSStyles, Format(
|
|
|
|
'</%s>', [ANodeName]));
|
|
|
|
end;
|
|
|
|
|
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]));
|
2014-07-11 22:43:00 +00:00
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
// Fonts
|
|
|
|
WriteFontList(FSStyles);
|
2014-07-11 22:43:00 +00:00
|
|
|
|
2014-07-12 22:12:38 +00:00
|
|
|
// Fill patterns
|
2014-07-13 22:09:27 +00:00
|
|
|
WriteFillList(FSStyles);
|
2014-07-12 22:12:38 +00:00
|
|
|
|
|
|
|
// Borders
|
2014-07-13 22:09:27 +00:00
|
|
|
WriteBorderList(FSStyles);
|
|
|
|
{
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<borders count="1">');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<border>',
|
|
|
|
'<left /><right /><top /><bottom /><diagonal />',
|
|
|
|
'</border>');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'</borders>');
|
2014-07-13 22:09:27 +00:00
|
|
|
}
|
2014-07-12 22:12:38 +00:00
|
|
|
// Style records
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSStyles,
|
2014-07-12 22:12:38 +00:00
|
|
|
'<cellStyleXfs count="1">',
|
2014-07-10 15:55:40 +00:00
|
|
|
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />',
|
2014-07-12 22:12:38 +00:00
|
|
|
'</cellStyleXfs>'
|
|
|
|
);
|
|
|
|
WriteStyleList(FSStyles, 'cellXfs');
|
|
|
|
|
|
|
|
// Cell style records
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<cellStyles count="1">',
|
|
|
|
'<cellStyle name="Normal" xfId="0" builtinId="0" />',
|
|
|
|
'</cellStyles>');
|
2014-07-12 22:12:38 +00:00
|
|
|
|
|
|
|
// Misc
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<dxfs count="0" />');
|
|
|
|
AppendToStream(FSStyles,
|
|
|
|
'<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleLight16" />');
|
2014-07-12 22:12:38 +00:00
|
|
|
|
2014-07-10 15:55:40 +00:00
|
|
|
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;
|
2014-07-11 20:00:49 +00:00
|
|
|
lCell: TCell;
|
2011-08-29 10:55:22 +00:00
|
|
|
AVLNode: TAVLTreeNode;
|
|
|
|
CellPosText: string;
|
2014-07-10 20:43:46 +00:00
|
|
|
value: Variant;
|
|
|
|
fn: 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 20:43:46 +00:00
|
|
|
// Create the stream
|
|
|
|
if (woSaveMemory in Workbook.WritingOptions) then begin
|
|
|
|
fn := IncludeTrailingPathDelimiter(GetTempDir);
|
|
|
|
fn := GetTempFileName(fn, Format('fpsSH%d-', [FCurSheetNum+1]));
|
|
|
|
FSSheets[FCurSheetNum] := TFileStream.Create(fn, fmCreate);
|
|
|
|
end else
|
|
|
|
FSSheets[FCurSheetNum] := TMemoryStream.Create;
|
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
|
|
|
|
2014-07-10 20:43:46 +00:00
|
|
|
if (woVirtualMode in Workbook.WritingOptions) and Assigned(Workbook.OnNeedCellData)
|
|
|
|
then begin
|
|
|
|
for r := 0 to Workbook.VirtualRowCount-1 do begin
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
2014-07-11 19:36:17 +00:00
|
|
|
'<row r="%d" spans="1:%d">', [r+1, Workbook.VirtualColCount]));
|
2014-07-10 20:43:46 +00:00
|
|
|
for c := 0 to Workbook.VirtualColCount-1 do begin
|
2014-07-11 20:00:49 +00:00
|
|
|
FillChar(lCell, SizeOf(lCell), 0);
|
2014-07-10 15:55:40 +00:00
|
|
|
CellPosText := CurSheet.CellPosToText(r, c);
|
2014-07-10 20:43:46 +00:00
|
|
|
value := varNull;
|
|
|
|
Workbook.OnNeedCellData(Workbook, r, c, value);
|
2014-07-11 20:00:49 +00:00
|
|
|
lCell.Row := r;
|
|
|
|
lCell.Col := c;
|
2014-07-10 20:43:46 +00:00
|
|
|
if VarIsNull(value) then
|
2014-07-11 20:00:49 +00:00
|
|
|
lCell.ContentType := cctEmpty
|
|
|
|
else
|
|
|
|
if VarIsNumeric(value) then begin
|
|
|
|
lCell.ContentType := cctNumber;
|
|
|
|
lCell.NumberValue := value;
|
|
|
|
end
|
|
|
|
{
|
|
|
|
else if VarIsDateTime(value) then begin
|
|
|
|
lCell.ContentType := cctNumber;
|
|
|
|
lCell.DateTimeValue := value;
|
|
|
|
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;
|
2014-07-10 20:43:46 +00:00
|
|
|
end;
|
2014-07-11 20:00:49 +00:00
|
|
|
WriteCellCallback(@lCell, FSSheets[FCurSheetNum]);
|
2011-08-29 10:55:22 +00:00
|
|
|
end;
|
2014-07-10 20:43:46 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</row>');
|
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
// The cells need to be written in order, row by row, cell by cell
|
|
|
|
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 begin
|
|
|
|
LCell.Row := r;
|
|
|
|
LCell.Col := c;
|
|
|
|
AVLNode := CurSheet.Cells.Find(@LCell);
|
|
|
|
if Assigned(AVLNode) then
|
2014-07-11 22:43:00 +00:00
|
|
|
WriteCellCallback(PCell(AVLNode.Data), FSSheets[FCurSheetNum])
|
2014-07-10 20:43:46 +00:00
|
|
|
else begin
|
|
|
|
CellPosText := CurSheet.CellPosToText(r, c);
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum], Format(
|
|
|
|
'<c r="%s">', [CellPosText]),
|
|
|
|
'<v></v>',
|
|
|
|
'</c>');
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</row>');
|
2011-08-29 10:55:22 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Footer
|
2014-07-10 15:55:40 +00:00
|
|
|
AppendToStream(FSSheets[FCurSheetNum],
|
|
|
|
'</sheetData>',
|
|
|
|
'</worksheet>');
|
2009-01-28 22:36:41 +00:00
|
|
|
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);
|
2012-04-27 08:01:15 +00:00
|
|
|
FPointSeparatorSettings := DefaultFormatSettings;
|
|
|
|
FPointSeparatorSettings.DecimalSeparator := '.';
|
2014-07-11 13:20:14 +00:00
|
|
|
|
|
|
|
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
|
|
|
|
FLimitations.MaxCols := 16384;
|
|
|
|
FLimitations.MaxRows := 1048576;
|
2012-04-27 08:01:15 +00:00
|
|
|
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
|
2014-07-10 20:43:46 +00:00
|
|
|
single xlsx file. }
|
2014-07-10 15:55:40 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.CreateStreams;
|
2014-07-10 20:43:46 +00:00
|
|
|
var
|
|
|
|
dir: String;
|
2014-07-10 15:55:40 +00:00
|
|
|
begin
|
2014-07-10 20:43:46 +00:00
|
|
|
if (woSaveMemory in Workbook.WritingOptions) then begin
|
|
|
|
dir := IncludeTrailingPathDelimiter(GetTempDir);
|
2014-07-11 13:20:14 +00:00
|
|
|
FSContentTypes := TFileStream.Create(GetTempFileName(dir, 'fpsCT'), fmCreate+fmOpenRead);
|
|
|
|
FSRelsRels := TFileStream.Create(GetTempFileName(dir, 'fpsRR'), fmCreate+fmOpenRead);
|
|
|
|
FSWorkbookRels := TFileStream.Create(GetTempFileName(dir, 'fpsWBR'), fmCreate+fmOpenRead);
|
|
|
|
FSWorkbook := TFileStream.Create(GetTempFileName(dir, 'fpsWB'), fmCreate+fmOpenRead);
|
|
|
|
FSStyles := TFileStream.Create(GetTempFileName(dir, 'fpsSTY'), fmCreate+fmOpenRead);
|
|
|
|
FSSharedStrings := TFileStream.Create(GetTempFileName(dir, 'fpsSST'), fmCreate+fmOpenRead);
|
|
|
|
FSSharedStrings_complete := TFileStream.Create(GetTempFileName(dir, 'fpsSSTc'), fmCreate+fmOpenRead);
|
2014-07-10 20:43:46 +00:00
|
|
|
end else begin;
|
|
|
|
FSContentTypes := TMemoryStream.Create;
|
|
|
|
FSRelsRels := TMemoryStream.Create;
|
|
|
|
FSWorkbookRels := TMemoryStream.Create;
|
|
|
|
FSWorkbook := TMemoryStream.Create;
|
|
|
|
FSStyles := TMemoryStream.Create;
|
|
|
|
FSSharedStrings := TMemoryStream.Create;
|
|
|
|
FSSharedStrings_complete := TMemoryStream.Create;
|
|
|
|
end;
|
2014-07-10 15:55:40 +00:00
|
|
|
// 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;
|
2014-07-10 20:43:46 +00:00
|
|
|
|
|
|
|
procedure DestroyStream(AStream: TStream);
|
|
|
|
var
|
|
|
|
fn: String;
|
|
|
|
begin
|
|
|
|
if AStream is TFileStream then begin
|
|
|
|
fn := TFileStream(AStream).Filename;
|
|
|
|
DeleteFile(fn);
|
|
|
|
end;
|
|
|
|
AStream.Free;
|
|
|
|
end;
|
|
|
|
|
2014-07-11 13:20:14 +00:00
|
|
|
var
|
|
|
|
stream: TStream;
|
2014-05-15 12:53:56 +00:00
|
|
|
begin
|
2014-07-10 20:43:46 +00:00
|
|
|
DestroyStream(FSContentTypes);
|
|
|
|
DestroyStream(FSRelsRels);
|
|
|
|
DestroyStream(FSWorkbookRels);
|
|
|
|
DestroyStream(FSWorkbook);
|
|
|
|
DestroyStream(FSStyles);
|
|
|
|
DestroyStream(FSSharedStrings);
|
|
|
|
DestroyStream(FSSharedStrings_complete);
|
2014-07-11 13:20:14 +00:00
|
|
|
for stream in FSSheets do DestroyStream(stream);
|
2014-07-10 15:55:40 +00:00
|
|
|
SetLength(FSSheets, 0);
|
2014-05-15 12:53:56 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-11 13:20:14 +00:00
|
|
|
{ Is called before zipping the individual file parts. Rewinds the streams. }
|
2014-07-10 20:43:46 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.ResetStreams;
|
|
|
|
var
|
2014-07-11 13:20:14 +00:00
|
|
|
stream: TStream;
|
2014-07-10 20:43:46 +00:00
|
|
|
begin
|
2014-07-11 13:20:14 +00:00
|
|
|
FSContentTypes.Position := 0;
|
|
|
|
FSRelsRels.Position := 0;
|
|
|
|
FSWorkbookRels.Position := 0;
|
|
|
|
FSWorkbook.Position := 0;
|
|
|
|
FSStyles.Position := 0;
|
|
|
|
FSSharedStrings_complete.Position := 0;
|
|
|
|
for stream in FSSheets do stream.Position := 0;
|
2014-07-10 20:43:46 +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-12 22:12:38 +00:00
|
|
|
{ Analyze the workbook and collect all information needed }
|
|
|
|
ListAllNumFormats;
|
|
|
|
ListAllFormattingStyles;
|
2014-07-13 22:09:27 +00:00
|
|
|
ListAllFills;
|
|
|
|
ListAllBorders;
|
2014-07-12 22:12:38 +00:00
|
|
|
|
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.
|
2014-07-10 20:43:46 +00:00
|
|
|
ResetStreams;
|
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;
|
|
|
|
|
2014-07-11 20:00:49 +00:00
|
|
|
procedure TsSpreadOOXMLWriter.WriteBlank(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; ACell: PCell);
|
|
|
|
var
|
|
|
|
cellPosText: String;
|
|
|
|
lStyleIndex: Integer;
|
|
|
|
begin
|
|
|
|
cellPosText := TsWorksheet.CellPosToText(ARow, ACol);
|
|
|
|
lStyleIndex := GetStyleIndex(ACell);
|
|
|
|
|
|
|
|
AppendToStream(AStream, Format(
|
|
|
|
'<c r="%s" s="%d">', [CellPosText, lStyleIndex]),
|
|
|
|
'<v></v>',
|
|
|
|
'</c>');
|
|
|
|
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-11 20:00:49 +00:00
|
|
|
AppendToStream(AStream, Format(
|
2014-07-10 15:55:40 +00:00
|
|
|
'<c r="%s" s="%d" t="s"><v>%d</v></c>', [CellPosText, lStyleIndex, FSharedStringsCount]));
|
2014-07-12 22:12:38 +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-11 20:00:49 +00:00
|
|
|
AppendToStream(AStream, Format(
|
2014-07-10 15:55:40 +00:00
|
|
|
'<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.
|
|
|
|
|