{------------------------------------------------------------------------------- Unit : xlsxml Implements a reader and writer for the SpreadsheetXML format. This document was introduced by Microsoft for Excel XP and 2003. REFERENCE: http://msdn.microsoft.com/en-us/library/aa140066%28v=office.15%29.aspx AUTHOR : Werner Pamler LICENSE : For details about the license, see the file COPYING.modifiedLGPL.txt included in the Lazarus distribution. -------------------------------------------------------------------------------} unit xlsxml; {$ifdef fpc} {$mode objfpc}{$H+} {$endif} interface uses Classes, SysUtils, laz2_xmlread, laz2_DOM, fpsTypes, fpsReaderWriter, fpsXMLCommon, xlsCommon; type { TsSpreadExcelXMLReader } TsSpreadExcelXMLReader = class(TsSpreadXMLReader) private FPointSeparatorSettings: TFormatSettings; function ExtractDateTime(AText: String): TDateTime; protected FFirstNumFormatIndexInFile: Integer; procedure AddBuiltinNumFormats; override; protected procedure ReadAlignment(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadBorder(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadBorders(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadCell(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow, ACol: Integer); procedure ReadCellProtection(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadComment(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ACell: PCell); procedure ReadFont(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadInterior(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadNumberFormat(ANode: TDOMNode; var AFormat: TsCellFormat); procedure ReadRow(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow: Integer); procedure ReadStyle(ANode: TDOMNode); procedure ReadStyles(ANode: TDOMNode); procedure ReadTable(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); procedure ReadWorksheetOptions(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); procedure ReadWorksheets(ANode: TDOMNode); public constructor Create(AWorkbook: TsBasicWorkbook); override; procedure ReadFromStream(AStream: TStream; APassword: String = ''; AParams: TsStreamParams = []); override; end; { TsSpreadExcelXMLWriter } TsSpreadExcelXMLWriter = class(TsCustomSpreadWriter) private FDateMode: TDateMode; FPointSeparatorSettings: TFormatSettings; function GetCommentStr(ACell: PCell): String; function GetFormulaStr(ACell: PCell): String; function GetFrozenPanesStr(AWorksheet: TsBasicWorksheet; AIndent: String): String; function GetHyperlinkStr(ACell: PCell): String; function GetIndexStr(AIndex: Integer): String; function GetLayoutStr(AWorksheet: TsBasicWorksheet): String; function GetMergeStr(ACell: PCell): String; function GetPageFooterStr(AWorksheet: TsBasicWorksheet): String; function GetPageHeaderStr(AWorksheet: TsBasicWorksheet): String; function GetPageMarginStr(AWorksheet: TsBasicWorksheet): String; function GetStyleStr(AFormatIndex: Integer): String; procedure WriteExcelWorkbook(AStream: TStream); procedure WriteStyle(AStream: TStream; AIndex: Integer); procedure WriteStyles(AStream: TStream); procedure WriteTable(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteWorksheet(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteWorksheetOptions(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteWorksheets(AStream: TStream); protected procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); override; procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal; const AValue: boolean; ACell: PCell); override; procedure WriteCellToStream(AStream: TStream; ACell: PCell); override; procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell); override; procedure WriteError(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TsErrorValue; ACell: PCell); override; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); override; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override; public constructor Create(AWorkbook: TsBasicWorkbook); override; procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); override; end; TExcelXmlSettings = record DateMode: TDateMode; end; var { Default parameters for reading/writing } ExcelXmlSettings: TExcelXmlSettings = ( DateMode: dm1900; ); sfidExcelXML: TsSpreadFormatID; implementation uses StrUtils, DateUtils, Math, fpsStrings, fpsClasses, fpspreadsheet, fpsUtils, fpsNumFormat, fpsHTMLUtils; const FMT_OFFSET = 61; INDENT1 = ' '; INDENT2 = ' '; INDENT3 = ' '; INDENT4 = ' '; INDENT5 = ' '; TABLE_INDENT = INDENT2; ROW_INDENT = INDENT3; COL_INDENT = INDENT3; CELL_INDENT = INDENT4; VALUE_INDENT = INDENT5; LF = LineEnding; const {TsFillStyle = ( fsNoFill, fsSolidFill, fsGray75, fsGray50, fsGray25, fsGray12, fsGray6, fsStripeHor, fsStripeVert, fsStripeDiagUp, fsStripeDiagDown, fsThinStripeHor, fsThinStripeVert, fsThinStripeDiagUp, fsThinStripeDiagDown, fsHatchDiag, fsThinHatchDiag, fsThickHatchDiag, fsThinHatchHor) } FILL_NAMES: array[TsFillStyle] of string = ( '', 'Solid', 'Gray75', 'Gray50', 'Gray25', 'Gray12', 'Gray0625', 'HorzStripe', 'VertStripe', 'DiagStripe', 'ReverseDiagStripe', 'ThinHorzStripe', 'ThinVertStripe', 'ThinDiagStripe', 'ThinReverseDiagStripe', 'DiagCross', 'ThinDiagCross', 'ThickDiagCross', 'ThinHorzCross' ); {TsCellBorder = (cbNorth, cbWest, cbEast, cbSouth, cbDiagUp, cbDiagDown); } BORDER_NAMES: array[TsCellBorder] of string = ( 'Top', 'Left', 'Right', 'Bottom', 'DiagonalRight', 'DiagonalLeft' ); {TsLineStyle = ( lsThin, lsMedium, lsDashed, lsDotted, lsThick, lsDouble, lsHair, lsMediumDash, lsDashDot, lsMediumDashDot, lsDashDotDot, lsMediumDashDotDot, lsSlantDashDot) } LINE_STYLES: array[TsLineStyle] of string = ( 'Continuous', 'Continuous', 'Dash', 'Dot', 'Continuous', 'Double', 'Continuous', 'Dash', 'DashDot', 'DashDot', 'DashDotDot', 'DashDotDot', 'SlantDashDot' ); LINE_WIDTHS: array[TsLineStyle] of Integer = ( 1, 2, 1, 1, 3, 3, 0, 2, 1, 2, 1, 2, 2 ); FALSE_TRUE: array[boolean] of string = ('False', 'True'); function GetCellContentTypeStr(ACell: PCell): String; begin case ACell^.ContentType of cctNumber : Result := 'Number'; cctUTF8String : Result := 'String'; cctDateTime : Result := 'DateTime'; cctBool : Result := 'Boolean'; cctError : Result := 'Error'; else raise EFPSpreadsheet.Create('Content type error in cell ' + GetCellString(ACell^.Row, ACell^.Col)); end; end; {@@ ---------------------------------------------------------------------------- Constructor of the ExcelXML reader -------------------------------------------------------------------------------} constructor TsSpreadExcelXMLReader.Create(AWorkbook: TsBasicWorkbook); begin inherited; // Cell formats (named "Styles" here). FCellFormatList := TsCellFormatList.Create(true); // is destroyed by ancestor // Special version of FormatSettings using a point decimal separator for sure. FPointSeparatorSettings := DefaultFormatSettings; FPointSeparatorSettings.DecimalSeparator := '.'; end; procedure TsSpreadExcelXMLReader.AddBuiltinNumFormats; begin FFirstNumFormatIndexInFile := 164; AddBuiltInBiffFormats( FNumFormatList, FWorkbook.FormatSettings, FFirstNumFormatIndexInFile-1 ); end; {@@ ---------------------------------------------------------------------------- Extracts the date/time value from the given string. The string is formatted as 'yyyy-mm-dd"T"hh:nn:ss.zzz' -------------------------------------------------------------------------------} function TsSpreadExcelXMLReader.ExtractDateTime(AText: String): TDateTime; var dateStr, timeStr: String; begin dateStr := Copy(AText, 1, 10); timeStr := Copy(AText, 12, MaxInt); Result := ScanDateTime('yyyy-mm-dd', dateStr) + ScanDateTime('hh:nn:ss.zzz', timeStr); end; {@@ ---------------------------------------------------------------------------- Reads the cell alignment from the given node attributes -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadAlignment(ANode: TDOMNode; var AFormat: TsCellFormat); var s: String; begin // Vertical alignment s := GetAttrValue(ANode, 'ss:Vertical'); if s <> '' then with AFormat do begin Include(UsedFormattingFields, uffVertAlign); case s of 'Top': VertAlignment := vaTop; 'Center': VertAlignment := vaCenter; 'Bottom': VertAlignment := vaBottom; else Exclude(UsedFormattingFields, uffVertAlign); end; end; // Horizontal alignment s := GetAttrValue(ANode, 'ss:Horizontal'); if s <> '' then with AFormat do begin Include(UsedFormattingFields, uffHorAlign); case s of 'Left': HorAlignment := haLeft; 'Center': HorAlignment := haCenter; 'Right': HorAlignment := haRight; else Exclude(UsedFormattingFields, uffHorAlign); end; end; // Vertical text s := GetAttrValue(ANode, 'ss:Rotate'); if s = '90' then with AFormat do begin TextRotation := rt90DegreeCounterClockwiseRotation; Include(UsedFormattingFields, uffTextRotation); end else if s = '-90' then with AFormat do begin TextRotation := rt90DegreeClockwiseRotation; Include(UsedFormattingFields, uffTextRotation); end; s := GetAttrValue(ANode, 'ss:VerticalText'); if s <> '' then with AFormat do begin TextRotation := rtStacked; Include(UsedFormattingFields, uffTextRotation); end; // Word wrap s := GetAttrValue(ANode, 'ss:WrapText'); if s = '1' then with AFormat do Include(UsedFormattingFields, uffWordWrap); // BiDi s := GetAttrValue(ANode, 'ss:ReadingOrder'); if s <> '' then with AFormat do begin case s of 'RightToLeft': BiDiMode := bdRTL; 'LeftToRight': BiDiMode := bdLTR; end; Include(UsedFormattingFields, uffBiDi); end; end; {@@ ---------------------------------------------------------------------------- Read a "Style/Borders/Border" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadBorder(ANode: TDOMNode; var AFormat: TsCellFormat); // var s, sw: String; b: TsCellBorder; begin AFormat.UsedFormattingFields := AFormat.UsedFormattingFields + [uffBorder]; // Border position s := GetAttrValue(ANode, 'ss:Position'); case s of 'Left': b := cbWest; 'Right': b := cbEast; 'Top': b := cbNorth; 'Bottom': b := cbSouth; 'DiagonalRight': b := cbDiagUp; 'DiagonalLeft': b := cbDiagDown; end; Include(AFormat.Border, b); // Border color s := GetAttrValue(ANode, 'ss:Color'); AFormat.BorderStyles[b].Color := HTMLColorStrToColor(s); // Line style s := GetAttrValue(ANode, 'ss:LineStyle'); sw := GetAttrValue(ANode, 'ss:Weight'); case s of 'Continuous': if sw = '1' then AFormat.BorderStyles[b].LineStyle := lsThin else if sw = '2' then AFormat.BorderStyles[b].LineStyle := lsMedium else if sw = '3' then AFormat.BorderStyles[b].LineStyle := lsThick else if sw = '' then AFormat.BorderStyles[b].LineStyle := lsHair; 'Double': AFormat.BorderStyles[b].LineStyle := lsDouble; 'Dot': AFormat.BorderStyles[b].LineStyle := lsDotted; 'Dash': if sw = '2' then AFormat.BorderStyles[b].LineStyle := lsMediumDash else AFormat.BorderStyles[b].LineStyle := lsDashed; 'DashDot': if sw = '2' then AFormat.BorderStyles[b].LineStyle := lsMediumDashDot else AFormat.BorderStyles[b].LineStyle := lsDashDot; 'DashDotDot': if sw = '2' then AFormat.BorderStyles[b].LineStyle := lsMediumDashDotDot else AFormat.BorderStyles[b].LineStyle := lsDashDotDot; 'SlantDashDot': AFormat.BorderStyles[b].LineStyle := lsSlantDashDot; end; end; {@@ ---------------------------------------------------------------------------- Reads the "Styles/Style/Borders" nodes -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadBorders(ANode: TDOMNode; var AFormat: TsCellFormat); var nodeName: String; begin if ANode = nil then exit; ANode := ANode.FirstChild; while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'Border' then ReadBorder(ANode, AFormat); ANode := ANode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads a "Worksheet/Table/Row/Cell" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadCell(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow, ACol: Integer); var sheet: TsWorksheet absolute AWorksheet; nodeName: string; s, st, sv: String; node: TDOMNode; err: TsErrorValue; cell: PCell; fmt: TsCellFormat; idx: Integer; mergedCols, mergedRows: Integer; begin if ANode = nil then exit; nodeName := ANode.NodeName; if nodeName <> 'Cell' then raise Exception.Create('[ReadCell] Only "Cell" nodes expected.'); cell := sheet.GetCell(ARow, ACol); s := GetAttrValue(ANode, 'ss:StyleID'); if s <> '' then begin idx := FCellFormatList.FindIndexOfName(s); if idx <> -1 then begin fmt := FCellFormatList.Items[idx]^; cell^.FormatIndex := TsWorkbook(FWorkbook).AddCellFormat(fmt); end; end; // Merged cells s := GetAttrValue(ANode, 'ss:MergeAcross'); if not ((s <> '') and TryStrToInt(s, mergedCols)) then mergedCols := 0; s := GetAttrValue(ANode, 'ss:MergeDown'); if not ((s <> '') and TryStrToint(s, mergedRows)) then mergedRows := 0; if (mergedCols > 0) or (mergedRows > 0) then sheet.MergeCells(ARow, ACol, ARow + mergedRows, ACol + mergedCols); // Hyperlink s := GetAttrValue(ANode, 'ss:HRef'); if s <> '' then begin st := GetAttrValue(ANode, 'x:HRefScreenTip'); sheet.WriteHyperlink(cell, s, st); end; // Cell data and comment node := ANode.FirstChild; if node = nil then sheet.WriteBlank(cell) else while node <> nil do begin nodeName := node.NodeName; if (nodeName = 'Data') or (nodeName = 'ss:Data') then begin sv := node.TextContent; st := GetAttrValue(node, 'ss:Type'); case st of 'String': sheet.WriteText(cell, sv); 'Number': sheet.WriteNumber(cell, StrToFloat(sv, FPointSeparatorSettings)); 'DateTime': sheet.WriteDateTime(cell, ExtractDateTime(sv)); 'Boolean': if sv = '1' then sheet.WriteBoolValue(cell, true) else if sv = '0' then sheet.WriteBoolValue(cell, false); 'Error': if TryStrToErrorValue(sv, err) then sheet.WriteErrorValue(cell, err); end; end else if (nodeName = 'Comment') then ReadComment(node, AWorksheet, cell); node := node.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads the "Styles/Style/Protection" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadCellProtection(ANode: TDOMNode; var AFormat: TsCellFormat); var s: String; begin if ANode = nil then exit; s := GetAttrValue(ANode, 'ss:Protected'); if s = '0' then Exclude(AFormat.Protection, cpLockCell); s := GetAttrValue(ANode, 'x:HideFormula'); if s = '1' then Include(AFormat.Protection, cpHideFormulas); if AFormat.Protection <> DEFAULT_CELL_PROTECTION then Include(AFormat.UsedFormattingFields, uffProtection); end; {@@ ---------------------------------------------------------------------------- Reads the "Worksheet/Table/Row/Cell/Comment" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadComment(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ACell: PCell); var txt: String; begin txt := ANode.TextContent; TsWorksheet(AWorksheet).WriteComment(ACell, txt); end; {@@ ---------------------------------------------------------------------------- Reads the "Styles/Style/Font" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLreader.ReadFont(ANode: TDOMNode; var AFormat: TsCellFormat); var book: TsWorkbook; fname: String; fsize: Single; fcolor: TsColor; fstyle: TsFontStyles; s: String; begin if ANode = nil then exit; book := TsWorkbook(FWorkbook); fname := GetAttrValue(ANode, 'ss:FontName'); if fname = '' then fname := book.GetDefaultFont.FontName; s := GetAttrValue(ANode, 'ss:Size'); if (s = '') or not TryStrToFloat(s, fsize, FPointSeparatorSettings) then fsize := book.GetDefaultFont.Size; s := GetAttrValue(ANode, 'ss:Color'); if s <> '' then fcolor := HTMLColorStrToColor(s) else fcolor := book.GetDefaultFont.Color; fstyle := []; s := GetAttrValue(ANode, 'ss:Bold'); if s = '1' then Include(fstyle, fssBold); s := GetAttrValue(ANode, 'ss:Italic'); if s = '1' then Include(fstyle, fssItalic); s := GetAttrValue(ANode, 'ss:UnderLine'); if s <> '' then Include(fstyle, fssUnderline); s := GetAttrValue(ANode, 'ss:StrikeThrough'); if s = '1' then Include(fstyle, fssStrikeout); AFormat.FontIndex := book.AddFont(fname, fsize, fstyle, fcolor); Include(AFormat.UsedFormattingFields, uffFont); end; {@@ ---------------------------------------------------------------------------- Reads the "Styles/Style/Interior" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadInterior(ANode: TDOMNode; var AFormat: TsCellFormat); var s: String; fs: TsFillStyle; begin if ANode = nil then exit; s := GetAttrValue(ANode, 'ss:Pattern'); if s = '' then exit; for fs in TsFillStyle do if FILL_NAMES[fs] = s then begin AFormat.Background.Style := fs; break; end; s := GetAttrValue(ANode, 'ss:PatternColor'); if s = '' then AFormat.Background.FgColor := scBlack else AFormat.Background.FgColor := HTMLColorStrToColor(s); s := GetAttrValue(ANode, 'ss:Color'); if s = '' then AFormat.Background.BgColor := scWhite else begin AFormat.Background.BgColor := HTMLColorStrToColor(s); if AFormat.Background.Style = fsSolidFill then AFormat.Background.FgColor := AFormat.Background.BgColor; end; Include(AFormat.UsedFormattingFields, uffBackground); end; {@@ ---------------------------------------------------------------------------- Reads a "Styles/Style/NumberFormat" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadNumberFormat(ANode: TDOMNode; var AFormat: TsCellFormat); var s: String; nf: TsNumberFormat = nfGeneral; nfs: String; begin if ANode = nil then exit; s := GetAttrValue(ANode, 'ss:Format'); case s of 'General': exit; 'Short Date': begin nf := nfShortDate; nfs := BuildDateTimeFormatString(nf, FWorkbook.FormatSettings); end; 'Short Time': begin nf := nfShortTime; nfs := BuildDateTimeFormatString(nf, FWorkbook.FormatSettings); end; else nfs := s; end; if nfs = '' then exit; AFormat.NumberFormatIndex := TsWorkbook(FWorkbook).AddNumberFormat(nfs); AFormat.NumberFormatStr := nfs; AFormat.NumberFormat := nf; Include(AFormat.UsedFormattingFields, uffNumberFormat); end; {@@ ---------------------------------------------------------------------------- Reads a "Worksheet/Table/Row" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadRow(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARow: Integer); var nodeName: String; s: String; c: Integer; begin c := 0; while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'Cell' then begin s := GetAttrValue(ANode, 'ss:Index'); if s <> '' then c := StrToInt(s) - 1; ReadCell(ANode, AWorksheet, ARow, c); inc(c); end; ANode := ANode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads a "Styles/Style" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadStyle(ANode: TDOMNode); var nodeName: String; fmt: TsCellFormat; s: String; id: Integer; idx: Integer; childNode: TDOMNode; begin // Respect ancestor of current style s := GetAttrValue(ANode, 'ss:Parent'); if s <> '' then begin idx := FCellFormatList.FindIndexOfName(s); if idx > -1 then fmt := FCellFormatList.Items[idx]^; end else InitFormatRecord(fmt); // ID of current style. We store it in the "Name" field of the TsCellFormat // because it is a string while ID is an Integer (mostly "s", but also // "Default"). fmt.Name := GetAttrValue(ANode, 'ss:ID'); if fmt.Name = 's125' then idx := 0; // Style elements childNode := ANode.FirstChild; while childNode <> nil do begin nodeName := childNode.NodeName; if nodeName = 'Alignment' then ReadAlignment(childNode, fmt) else if nodeName = 'Borders' then ReadBorders(childNode, fmt) else if nodeName = 'Interior' then ReadInterior(childNode, fmt) else if nodeName = 'Font' then ReadFont(childNode, fmt) else if nodeName = 'NumberFormat' then ReadNumberFormat(childnode, fmt) else if nodeName = 'Protection' then ReadCellProtection(childNode, fmt); childNode := childNode.NextSibling; end; FCellFormatList.Add(fmt); end; {@@ ---------------------------------------------------------------------------- Reads the "Styles" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadStyles(ANode: TDOMNode); var nodeName: String; styleNode: TDOMNode; begin if ANode = nil then exit; styleNode := ANode.FirstChild; while styleNode <> nil do begin nodeName := styleNode.NodeName; if nodeName = 'Style' then ReadStyle(styleNode); styleNode := styleNode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads the "Worksheet/Table" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadTable(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); var sheet: TsWorksheet absolute AWorksheet; nodeName: String; s: String; r, c: Integer; x: Double; idx: Integer; fmt: TsCellFormat; begin r := 0; c := 0; while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'Column' then begin // Column index s := GetAttrValue(ANode, 'ss:Index'); if (s <> '') and TryStrToInt(s, c) then dec(c); // Column width, in Points s := GetAttrValue(ANode, 'ss:Width'); if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then sheet.WriteColWidth(c, x, suPoints); // Column format s := GetAttrValue(ANode, 'ss:StyleID'); if s <> '' then begin idx := FCellFormatList.FindIndexOfName(s); if idx <> -1 then begin fmt := FCellFormatList.Items[idx]^; idx := TsWorkbook(FWorkbook).AddCellFormat(fmt); sheet.WriteColFormatIndex(c, idx); end; end; inc(c); end else if nodeName = 'Row' then begin // Default column width s := GetAttrValue(ANode, 'ss:DefaultColumnWidth'); if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then sheet.WriteDefaultColWidth(x, suPoints); // Default row height s := GetAttrValue(ANode, 'ss:DefaultRowHeight'); if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then sheet.WriteDefaultRowHeight(x, suPoints); // Index s := GetAttrValue(ANode, 'ss:Index'); if s <> '' then r := StrToInt(s) - 1; // Height s := GetAttrValue(ANode, 'ss:Height'); if (s <> '') and TryStrToFloat(s, x, FPointSeparatorSettings) then sheet.WriteRowHeight(r, x, suPoints); // Row format s := GetAttrValue(ANode, 'ss:StyleID'); if s <> '' then begin idx := FCellFormatList.FindIndexOfName(s); if idx <> -1 then begin fmt := FCellFormatList.Items[idx]^; idx := TsWorkbook(FWorkbook).AddCellFormat(fmt); sheet.WriteRowFormatIndex(r, idx); end; end; // Cells in row ReadRow(ANode.FirstChild, AWorksheet, r); inc(r); end; ANode := ANode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads the "Worksheet" node -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadWorksheet(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); var nodeName: String; s: String; begin while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'Table' then ReadTable(ANode.FirstChild, AWorksheet) else if nodeName = 'WorksheetOptions' then ReadWorksheetOptions(ANode, AWorksheet); ANode := ANode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads the "Worksheet/WorksheetOptions" nodes -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadWorksheetOptions(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); begin // to do end; {@@ ---------------------------------------------------------------------------- Reads the "Worksheet" nodes -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadWorksheets(ANode: TDOMNode); var nodeName: String; s: String; begin while ANode <> nil do begin nodeName := ANode.NodeName; if nodeName = 'Worksheet' then begin s := GetAttrValue(ANode, 'ss:Name'); if s <> '' then begin // the case of '' should not happen FWorksheet := TsWorkbook(FWorkbook).AddWorksheet(s); ReadWorksheet(ANode.FirstChild, FWorksheet); end; end; ANode := ANode.NextSibling; end; end; {@@ ---------------------------------------------------------------------------- Reads the workbook from the specified stream -------------------------------------------------------------------------------} procedure TsSpreadExcelXMLReader.ReadFromStream(AStream: TStream; APassword: String = ''; AParams: TsStreamParams = []); var doc: TXMLDocument; begin try ReadXMLStream(doc, AStream); ReadStyles(doc.DocumentElement.FindNode('Styles')); ReadWorksheets(doc.DocumentElement.FindNode('Worksheet')); finally doc.Free; end; end; {@@ ---------------------------------------------------------------------------- Constructor of the ExcelXML writer Defines the date mode and the limitations of the file format. Initializes the format settings to be used when writing to xml. -------------------------------------------------------------------------------} constructor TsSpreadExcelXMLWriter.Create(AWorkbook: TsBasicWorkbook); begin inherited Create(AWorkbook); // Initial base date in case it won't be set otherwise. // Use 1900 to get a bit more range between 1900..1904. FDateMode := ExcelXMLSettings.DateMode; // Special version of FormatSettings using a point decimal separator for sure. FPointSeparatorSettings := DefaultFormatSettings; FPointSeparatorSettings.DecimalSeparator := '.'; // http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications FLimitations.MaxColCount := 256; FLimitations.MaxRowCount := 65536; end; function TsSpreadExcelXMLWriter.GetCommentStr(ACell: PCell): String; var comment: PsComment; begin Result := ''; comment := (FWorksheet as TsWorksheet).FindComment(ACell); if Assigned(comment) then Result := INDENT1 + '' + comment^.Text + '' + LF + CELL_INDENT; // If there will be some rich-text-like formatting in the future, use // Result := ''+comment^.Text+'': end; function TsSpreadExcelXMLWriter.GetFormulaStr(ACell: PCell): String; begin if HasFormula(ACell) then begin Result := UTF8TextToXMLText((FWorksheet as TsWorksheet).ConvertFormulaDialect(ACell, fdExcelR1C1)); Result := ' ss:Formula="=' + Result + '"'; end else Result := ''; end; function TsSpreadExcelXMLWriter.GetFrozenPanesStr(AWorksheet: TsBasicWorksheet; AIndent: String): String; var activePane: Integer; sheet: TsWorksheet absolute AWorksheet; begin if (soHasFrozenPanes in sheet.Options) then begin Result := AIndent + '' + LF + AIndent + '' + LF; if sheet.LeftPaneWidth > 0 then Result := Result + AIndent + '1' + LF + AIndent + '' + IntToStr(sheet.LeftPaneWidth) + '' + LF; if sheet.TopPaneHeight > 0 then Result := Result + AIndent + '1' + LF + AIndent + '' + IntToStr(sheet.TopPaneHeight) + '' + LF; if (sheet.LeftPaneWidth = 0) and (sheet.TopPaneHeight = 0) then activePane := 3 else if (sheet.LeftPaneWidth = 0) then activePane := 2 else if (sheet.TopPaneHeight = 0) then activePane := 1 else activePane := 0; Result := Result + AIndent + '' + IntToStr(activePane) + '' + LF; end else Result := ''; end; function TsSpreadExcelXMLWriter.GetHyperlinkStr(ACell: PCell): String; var hyperlink: PsHyperlink; begin Result := ''; hyperlink := (FWorksheet as TsWorksheet).FindHyperlink(ACell); if Assigned(hyperlink) then Result := ' ss:HRef="' + hyperlink^.Target + '"'; end; function TsSpreadExcelXMLWriter.GetIndexStr(AIndex: Integer): String; begin Result := Format(' ss:Index="%d"', [AIndex]); end; function TsSpreadExcelXMLWriter.GetLayoutStr(AWorksheet: TsBasicWorksheet): String; var sheet: TsWorksheet absolute AWorksheet; begin Result := ''; if sheet.PageLayout.Orientation = spoLandscape then Result := Result + ' x:Orientation="Landscape"'; if (poHorCentered in sheet.PageLayout.Options) then Result := Result + ' x:CenterHorizontal="1"'; if (poVertCentered in sheet.PageLayout.Options) then Result := Result + ' x:CenterVertical="1"'; if (poUseStartPageNumber in sheet.PageLayout.Options) then Result := Result + ' x:StartPageNumber="' + IntToStr(sheet.PageLayout.StartPageNumber) + '"'; Result := ''; end; function TsSpreadExcelXMLWriter.GetMergeStr(ACell: PCell): String; var r1, c1, r2, c2: Cardinal; begin Result := ''; if (FWorksheet as TsWorksheet).IsMerged(ACell) then begin (FWorksheet as TsWorksheet).FindMergedRange(ACell, r1, c1, r2, c2); if c2 > c1 then Result := Result + Format(' ss:MergeAcross="%d"', [c2-c1]); if r2 > r1 then Result := Result + Format(' ss:MergeDown="%d"', [r2-r1]); end; end; function TsSpreadExcelXMLWriter.GetPageFooterStr( AWorksheet: TsBasicWorksheet): String; var sheet: TsWorksheet absolute AWorksheet; begin Result := Format('x:Margin="%g"', [mmToIn(sheet.PageLayout.FooterMargin)], FPointSeparatorSettings); if (sheet.PageLayout.Footers[HEADER_FOOTER_INDEX_ALL] <> '') then Result := Result + ' x:Data="' + UTF8TextToXMLText(sheet.PageLayout.Footers[HEADER_FOOTER_INDEX_ALL], true) + '"'; Result := '