{
xlsxooxml.pas
Writes an OOXML (Office Open XML) document
An OOXML document is a compressed ZIP file with the following files inside:
[Content_Types].xml -
_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
...
xl\worksheets\sheetN.xml
Specifications obtained from:
http://openxmldeveloper.org/default.aspx
AUTHORS: Felipe Monteiro de Carvalho
}
unit xlsxooxml;
{$ifdef fpc}
{$mode delphi}
{$endif}
interface
uses
Classes, SysUtils,
fpszipper, {NOTE: fpszipper is the latest zipper.pp Change to standard zipper when FPC 2.8 is released }
{xmlread, DOM,} AVL_Tree,
fpspreadsheet;
type
{ TsSpreadOOXMLWriter }
TsSpreadOOXMLWriter = class(TsCustomSpreadWriter)
protected
FPointSeparatorSettings: TFormatSettings;
{ Strings with the contents of files }
FContentTypes: string;
FRelsRels: string;
FWorkbook, FWorkbookRels, FStyles, FSharedStrings: string;
FSheets: array of string;
FSharedStringsCount: Integer;
{ Streams with the contents of files }
FSContentTypes: TStringStream;
FSRelsRels: TStringStream;
FSWorkbook, FSWorkbookRels, FSStyles, FSSharedStrings: TStringStream;
FSSheets: array of TStringStream;
FCurSheetNum: Integer;
{ Routines to write those files }
procedure WriteGlobalFiles(AData: TsWorkbook);
procedure WriteContent(AData: TsWorkbook);
procedure WriteWorksheet(CurSheet: TsWorksheet);
function GetStyleIndex(ACell: PCell): Cardinal;
public
constructor Create; override;
destructor Destroy; override;
{ General writing methods }
procedure WriteStringToFile(AFileName, AString: string);
procedure WriteToFile(const AFileName: string; AData: TsWorkbook;
const AOverwriteExisting: Boolean = False); override;
procedure WriteToStream(AStream: TStream; AData: TsWorkbook); override;
{ Record writing methods }
procedure WriteLabel(AStream: TStream; const ARow, ACol: Word; const AValue: string; ACell: PCell); override;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override;
end;
implementation
const
{ OOXML general XML constants }
XML_HEADER = '';
{ OOXML Directory structure constants }
OOXML_PATH_TYPES = '[Content_Types].xml';
OOXML_PATH_RELS = '_rels' + PathDelim;
OOXML_PATH_RELS_RELS = '_rels' + PathDelim + '.rels';
OOXML_PATH_XL = 'xl' + PathDelim;
OOXML_PATH_XL_RELS = 'xl' + PathDelim + '_rels' + PathDelim;
OOXML_PATH_XL_RELS_RELS = 'xl' + PathDelim + '_rels' + PathDelim + 'workbook.xml.rels';
OOXML_PATH_XL_WORKBOOK = 'xl' + PathDelim + 'workbook.xml';
OOXML_PATH_XL_STYLES = 'xl' + PathDelim + 'styles.xml';
OOXML_PATH_XL_STRINGS = 'xl' + PathDelim + 'sharedStrings.xml';
OOXML_PATH_XL_WORKSHEETS = 'xl' + PathDelim + 'worksheets' + PathDelim;
{ 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';
{ TsSpreadOOXMLWriter }
procedure TsSpreadOOXMLWriter.WriteGlobalFiles(AData: TsWorkbook);
var
i: Integer;
begin
// WriteCellsToStream(AStream, AData.GetFirstWorksheet.FCells);
FContentTypes :=
XML_HEADER + LineEnding +
'' + LineEnding +
// ' ' + LineEnding +
// ' ' + LineEnding +
' ' + LineEnding +
//
//
' ' + LineEnding +
' ' + LineEnding;
for i := 1 to AData.GetWorksheetCount do
begin
FContentTypes := FContentTypes +
Format(' ', [i, MIME_WORKSHEET]) + LineEnding;
end;
FContentTypes := FContentTypes +
' ' + LineEnding +
' ' + LineEnding +
'';
FRelsRels :=
XML_HEADER + LineEnding +
'' + LineEnding +
'' + LineEnding +
'';
FStyles :=
XML_HEADER + LineEnding +
'' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
'';
end;
procedure TsSpreadOOXMLWriter.WriteContent(AData: TsWorkbook);
var
i: Integer;
begin
{ Workbook relations - Mark relation to all sheets }
FWorkbookRels :=
XML_HEADER + LineEnding +
'' + LineEnding +
'' + LineEnding +
'' + LineEnding;
for i := 1 to AData.GetWorksheetCount do
begin
FWorkbookRels := FWorkbookRels +
Format('', [SCHEMAS_WORKSHEET, i, i+2]) + LineEnding;
end;
FWorkbookRels := FWorkbookRels +
'';
// Global workbook data - Mark all sheets
FWorkbook :=
XML_HEADER + LineEnding +
'' + LineEnding +
' ' + LineEnding + // lastEdited="4" lowestEdited="4" rupBuild="4505"
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding;
FWorkbook := FWorkbook + ' ' + LineEnding;
for i := 1 to AData.GetWorksheetCount do
FWorkbook := FWorkbook +
Format(' ', [i, i, i+2]) + LineEnding;
FWorkbook := FWorkbook + ' ' + LineEnding;
FWorkbook := FWorkbook +
' ' + LineEnding +
'';
// Preparation for Shared strings
FSharedStringsCount := 0;
FSharedStrings := '';
// Write all worksheets, which fills also FSharedStrings
SetLength(FSheets, 0);
for i := 0 to AData.GetWorksheetCount - 1 do
begin
WriteWorksheet(Adata.GetWorksheetByIndex(i));
end;
// Finalization of the shared strings document
FSharedStrings :=
XML_HEADER + LineEnding +
'' + LineEnding +
FSharedStrings +
'';
end;
{
FSheets[CurStr] :=
XML_HEADER + LineEnding +
'' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 1' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 2' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 3' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 4' + LineEnding +
' ' + LineEnding +
'
' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 0' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 1' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 2' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' 3' + LineEnding +
' ' + LineEnding +
'
' + LineEnding +
' ' + LineEnding +
'';
}
procedure TsSpreadOOXMLWriter.WriteWorksheet(CurSheet: TsWorksheet);
var
j, k: Integer;
CurCell: PCell;
CurRow: array of PCell;
LastColNum: Cardinal;
LCell: TCell;
AVLNode: TAVLTreeNode;
CellPosText: string;
begin
FCurSheetNum := Length(FSheets);
SetLength(FSheets, FCurSheetNum + 1);
LastColNum := CurSheet.GetLastColNumber;
// Header
FSheets[FCurSheetNum] :=
XML_HEADER + LineEnding +
'' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding +
' ' + LineEnding;
// The cells need to be written in order, row by row, cell by cell
for j := 0 to CurSheet.GetLastRowNumber do
begin
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
Format(' ', [j+1,LastColNum+1]) + LineEnding;
// Write cells from this row.
for k := 0 to LastColNum do
begin
LCell.Row := j;
LCell.Col := k;
AVLNode := CurSheet.Cells.Find(@LCell);
if Assigned(AVLNode) then
WriteCellCallback(PCell(AVLNode.Data), nil)
else
begin
CellPosText := CurSheet.CellPosToText(j, k);
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
Format(' ', [CellPosText]) + LineEnding +
' ' + LineEnding +
' ' + LineEnding;
end;
end;
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
'
' + LineEnding;
end;
// Footer
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
' ' + LineEnding +
'';
end;
// 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;
constructor TsSpreadOOXMLWriter.Create;
begin
inherited Create;
FPointSeparatorSettings := DefaultFormatSettings;
FPointSeparatorSettings.DecimalSeparator := '.';
end;
destructor TsSpreadOOXMLWriter.Destroy;
begin
SetLength(FSheets, 0);
SetLength(FSSheets, 0);
inherited Destroy;
end;
{
Writes a string to a file. Helper convenience method.
}
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;
{
Writes an OOXML document to the disc
}
procedure TsSpreadOOXMLWriter.WriteToFile(const AFileName: string;
AData: TsWorkbook; const AOverwriteExisting: Boolean);
var
lStream: TFileStream;
begin
lStream:=TFileStream.Create(AFileName,fmCreate);
try
WriteToStream(lStream, AData);
finally
FreeAndNil(lStream);
end;
end;
procedure TsSpreadOOXMLWriter.WriteToStream(AStream: TStream; AData: TsWorkbook);
var
FZip: TZipper;
i: Integer;
begin
{ Fill the strings with the contents of the files }
WriteGlobalFiles(AData);
WriteContent(AData);
{ Write the data to streams }
FSContentTypes := TStringStream.Create(FContentTypes);
FSRelsRels := TStringStream.Create(FRelsRels);
FSWorkbookRels := TStringStream.Create(FWorkbookRels);
FSWorkbook := TStringStream.Create(FWorkbook);
FSStyles := TStringStream.Create(FStyles);
FSSharedStrings := TStringStream.Create(FSharedStrings);
SetLength(FSSheets, Length(FSheets));
for i := 0 to Length(FSheets) - 1 do
FSSheets[i] := TStringStream.Create(FSheets[i]);
{ 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);
FZip.Entries.AddFileEntry(FSSharedStrings, OOXML_PATH_XL_STRINGS);
for i := 0 to Length(FSheets) - 1 do
FZip.Entries.AddFileEntry(FSSheets[i], OOXML_PATH_XL_WORKSHEETS + 'sheet' + IntToStr(i + 1) + '.xml');
FZip.SaveToStream(AStream);
finally
FSContentTypes.Free;
FSRelsRels.Free;
FSWorkbookRels.Free;
FSWorkbook.Free;
FSStyles.Free;
FSSharedStrings.Free;
for i := 0 to Length(FSSheets) - 1 do
FSSheets[i].Free;
FZip.Free;
end;
end;
{
Writes a string to the sheet
}
procedure TsSpreadOOXMLWriter.WriteLabel(AStream: TStream; const ARow,
ACol: Word; const AValue: string; ACell: PCell);
var
CellPosText: string;
lStyleIndex: Cardinal;
begin
FSharedStrings := FSharedStrings +
' ' + LineEnding +
Format(' %s', [AValue]) + LineEnding +
' ' + LineEnding;
CellPosText := TsWorksheet.CellPosToText(ARow, ACol);
lStyleIndex := GetStyleIndex(ACell);
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
Format(' %d', [CellPosText, lStyleIndex, FSharedStringsCount]) + LineEnding;
Inc(FSharedStringsCount);
end;
{
Writes a number (64-bit IEE 754 floating point) to the sheet
}
procedure TsSpreadOOXMLWriter.WriteNumber(AStream: TStream; const ARow,
ACol: Cardinal; const AValue: double; ACell: PCell);
var
CellPosText: String;
CellValueText: String;
begin
CellPosText := TsWorksheet.CellPosToText(ARow, ACol);
CellValueText := Format('%g', [AValue], FPointSeparatorSettings);
FSheets[FCurSheetNum] := FSheets[FCurSheetNum] +
Format(' %s', [CellPosText, CellValueText]) + LineEnding;
end;
{
Registers this reader / writer on fpSpreadsheet
}
initialization
RegisterSpreadFormat(TsCustomSpreadReader, TsSpreadOOXMLWriter, sfOOXML);
end.