{ 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.