unit formattests;

{$mode objfpc}{$H+}

interface
{ Formatted date/time/number tests
This unit tests writing out to and reading back from files.
Tests that verify reading from an Excel/LibreOffice/OpenOffice file are located in other
units (e.g. datetests).
}

uses
  {$IFDEF Unix}
  //required for formatsettings
  clocale,
  {$ENDIF}
  // Not using Lazarus package as the user may be working with multiple versions
  // Instead, add .. to unit search path
  Classes, SysUtils, fpcunit, testutils, testregistry,
  fpsallformats, fpspreadsheet, xlsbiff8 {and a project requirement for lclbase for utf8 handling},
  testsutility;

var
  // Norm to test against - list of strings that should occur in spreadsheet
  SollNumberStrings: array[0..6, 0..7] of string;
  SollNumbers: array[0..6] of Double;
  SollNumberFormats: array[0..7] of TsNumberFormat;
  SollNumberDecimals: array[0..7] of word;

  SollDateTimeStrings: array[0..4, 0..9] of string;
  SollDateTimes: array[0..4] of TDateTime;
  SollDateTimeFormats: array[0..9] of TsNumberFormat;
  SollDateTimeFormatStrings: array[0..9] of String;

  SollColWidths: array[0..1] of Single;
  SollRowHeights: Array[0..2] of Single;
  SollBorders: array[0..19] of TsCellBorders;
  SollBorderLineStyles: array[0..6] of TsLineStyle;
  SollBorderColors: array[0..5] of TsColor;

  procedure InitSollFmtData;

type
  { TSpreadWriteReadFormatTests }
  //Write to xls/xml file and read back
  TSpreadWriteReadFormatTests = class(TTestCase)
  private
  protected
    // Set up expected values:
    procedure SetUp; override;
    procedure TearDown; override;

    // Test alignments
    procedure TestWriteRead_Alignment(AFormat: TsSpreadsheetFormat);
    // Test border
    procedure TestWriteRead_Border(AFormat: TsSpreadsheetFormat);
    // Test border styles
    procedure TestWriteRead_BorderStyles(AFormat: TsSpreadsheetFormat);
    // Test column widths
    procedure TestWriteRead_ColWidths(AFormat: TsSpreadsheetFormat);
    // Test row heights
    procedure TestWriteRead_RowHeights(AFormat: TsSpreadsheetFormat);
    // Test text rotation
    procedure TestWriteRead_TextRotation(AFormat:TsSpreadsheetFormat);
    // Test word wrapping
    procedure TestWriteRead_WordWrap(AFormat: TsSpreadsheetFormat);
    // Test number formats
    procedure TestWriteRead_NumberFormats(AFormat: TsSpreadsheetFormat);
    // Repeat with date/times
    procedure TestWriteRead_DateTimeFormats(AFormat: TsSpreadsheetFormat);
    // Test merged cells
    procedure TestWriteRead_MergedCells(AFormat: TsSpreadsheetFormat);

  published
    // Writes out numbers & reads back.
    // If previous read tests are ok, this effectively tests writing.

    { BIFF2 Tests }
    procedure TestWriteRead_BIFF2_Alignment;
    procedure TestWriteRead_BIFF2_Border;
    procedure TestWriteRead_BIFF2_ColWidths;
    procedure TestWriteRead_BIFF2_RowHeights;
    procedure TestWriteRead_BIFF2_DateTimeFormats;
    procedure TestWriteRead_BIFF2_MergedCells;
    procedure TestWriteRead_BIFF2_NumberFormats;
    // These features are not supported by Excel2 --> no test cases required!
    // - BorderStyle
    // - TextRotation
    // - Wordwrap

    { BIFF5 Tests }
    procedure TestWriteRead_BIFF5_Alignment;
    procedure TestWriteRead_BIFF5_Border;
    procedure TestWriteRead_BIFF5_BorderStyles;
    procedure TestWriteRead_BIFF5_ColWidths;
    procedure TestWriteRead_BIFF5_RowHeights;
    procedure TestWriteRead_BIFF5_DateTimeFormats;
    procedure TestWriteRead_BIFF5_MergedCells;
    procedure TestWriteRead_BIFF5_NumberFormats;
    procedure TestWriteRead_BIFF5_TextRotation;
    procedure TestWriteRead_BIFF5_WordWrap;

    { BIFF8 Tests }
    procedure TestWriteRead_BIFF8_Alignment;
    procedure TestWriteRead_BIFF8_Border;
    procedure TestWriteRead_BIFF8_BorderStyles;
    procedure TestWriteRead_BIFF8_ColWidths;
    procedure TestWriteRead_BIFF8_RowHeights;
    procedure TestWriteRead_BIFF8_DateTimeFormats;
    procedure TestWriteRead_BIFF8_MergedCells;
    procedure TestWriteRead_BIFF8_NumberFormats;
    procedure TestWriteRead_BIFF8_TextRotation;
    procedure TestWriteRead_BIFF8_WordWrap;

    { ODS Tests }
    procedure TestWriteRead_ODS_Alignment;
    procedure TestWriteRead_ODS_Border;
    procedure TestWriteRead_ODS_BorderStyles;
    procedure TestWriteRead_ODS_ColWidths;
    procedure TestWriteRead_ODS_RowHeights;
    procedure TestWriteRead_ODS_DateTimeFormats;
    procedure TestWriteRead_ODS_MergedCells;
    procedure TestWriteRead_ODS_NumberFormats;
    procedure TestWriteRead_ODS_TextRotation;
    procedure TestWriteRead_ODS_WordWrap;

    { OOXML Tests }
    procedure TestWriteRead_OOXML_Alignment;
    procedure TestWriteRead_OOXML_Border;
    procedure TestWriteRead_OOXML_BorderStyles;
    procedure TestWriteRead_OOXML_ColWidths;
    procedure TestWriteRead_OOXML_RowHeights;
    procedure TestWriteRead_OOXML_DateTimeFormats;
    procedure TestWriteRead_OOXML_MergedCells;
    procedure TestWriteRead_OOXML_NumberFormats;
    procedure TestWriteRead_OOXML_TextRotation;
    procedure TestWriteRead_OOXML_WordWrap;

  end;

implementation

uses
  TypInfo, fpsutils;

const
  FmtNumbersSheet = 'NumbersFormat'; //let's distinguish it from the regular numbers sheet
  FmtDateTimesSheet = 'DateTimesFormat';
  ColWidthSheet = 'ColWidths';
  RowHeightSheet = 'RowHeights';
  BordersSheet = 'CellBorders';
  AlignmentSheet = 'TextAlignments';
  TextRotationSheet = 'TextRotation';
  WordwrapSheet = 'Wordwrap';

// Initialize array with variables that represent the values
// we expect to be in the test spreadsheet files.
//
// When adding tests, add values to this array
// and increase array size in variable declaration
procedure InitSollFmtData;
var
  i: Integer;
  fs: TFormatSettings;
  myworkbook: TsWorkbook;
begin
  // Set up norm - MUST match spreadsheet cells exactly

  // The workbook uses a slightly modified copy of the DefaultFormatSettings
  // We create a copy here in order to better define the predicted strings.
  myWorkbook := TsWorkbook.Create;
  fs := MyWorkbook.FormatSettings;
  myWorkbook.Free;

  // Numbers
  SollNumbers[0] := 0.0;
  SollNumbers[1] := 1.0;
  SollNumbers[2] := -1.0;
  SollNumbers[3] :=  1.23456E6;
  SollNumbers[4] := -1.23456E6;
  SollNumbers[5] :=  1.23456E-6;
  SollNumbers[6] := -1.23456E-6;

  SollNumberFormats[0] := nfGeneral;      SollNumberDecimals[0] := 0;
  SollNumberFormats[1] := nfFixed;        SollNumberDecimals[1] := 0;
  SollNumberFormats[2] := nfFixed;        SollNumberDecimals[2] := 2;
  SollNumberFormats[3] := nfFixedTh;      SollNumberDecimals[3] := 0;
  SollNumberFormats[4] := nfFixedTh;      SollNumberDecimals[4] := 2;
  SollNumberFormats[5] := nfExp;          SollNumberDecimals[5] := 2;
  SollNumberFormats[6] := nfPercentage;   SollNumberDecimals[6] := 0;
  SollNumberFormats[7] := nfPercentage;   SollNumberDecimals[7] := 2;

  for i:=Low(SollNumbers) to High(SollNumbers) do
  begin
    SollNumberStrings[i, 0] := FloatToStr(SollNumbers[i], fs);
    SollNumberStrings[i, 1] := FormatFloat('0', SollNumbers[i], fs);
    SollNumberStrings[i, 2] := FormatFloat('0.00', SollNumbers[i], fs);
    SollNumberStrings[i, 3] := FormatFloat('#,##0', SollNumbers[i], fs);
    SollNumberStrings[i, 4] := FormatFloat('#,##0.00', SollNumbers[i], fs);
    SollNumberStrings[i, 5] := FormatFloat('0.00E+00', SollNumbers[i], fs);
    SollNumberStrings[i, 6] := FormatFloat('0', SollNumbers[i]*100, fs) + '%';
    SollNumberStrings[i, 7] := FormatFloat('0.00', SollNumbers[i]*100, fs) + '%';
  end;

  // Date/time values
  SollDateTimes[0] := EncodeDate(2012, 1, 12) + EncodeTime(13, 14, 15, 567);
  SolLDateTimes[1] := EncodeDate(2012, 2, 29) + EncodeTime(0, 0, 0, 1);
  SollDateTimes[2] := EncodeDate(2040, 12, 31) + EncodeTime(12, 0, 0, 0);
  SollDateTimes[3] := 1 + EncodeTime(3,45, 0, 0);
  SollDateTimes[4] := EncodeTime(12, 0, 0, 0);

  SollDateTimeFormats[0] := nfShortDateTime;   SollDateTimeFormatStrings[0] := '';
  SollDateTimeFormats[1] := nfShortDate;       SollDateTimeFormatStrings[1] := '';
  SollDateTimeFormats[2] := nfShortTime;       SollDateTimeFormatStrings[2] := '';
  SollDateTimeFormats[3] := nfLongTime;        SollDateTimeFormatStrings[3] := '';
  SollDateTimeFormats[4] := nfShortTimeAM;     SollDateTimeFormatStrings[4] := '';
  SollDateTimeFormats[5] := nfLongTimeAM;      SollDateTimeFormatStrings[5] := '';
  SollDateTimeFormats[6] := nfCustom;          SollDateTimeFormatStrings[6] := 'dd/mmm';
  SolLDateTimeFormats[7] := nfCustom;          SollDateTimeFormatStrings[7] := 'mmm/yy';
  SollDateTimeFormats[8] := nfCustom;          SollDateTimeFormatStrings[8] := 'nn:ss';
  SollDateTimeFormats[9] := nfTimeInterval;    SollDateTimeFormatStrings[9] := '';

  for i:=Low(SollDateTimes) to High(SollDateTimes) do
  begin
    SollDateTimeStrings[i, 0] := DateToStr(SollDateTimes[i], fs) + ' ' + FormatDateTime('t', SollDateTimes[i], fs);
    SollDateTimeStrings[i, 1] := DateToStr(SollDateTimes[i], fs);
    SollDateTimeStrings[i, 2] := FormatDateTime(fs.ShortTimeFormat, SollDateTimes[i], fs);
    SolLDateTimeStrings[i, 3] := FormatDateTime(fs.LongTimeFormat, SollDateTimes[i], fs);
    SollDateTimeStrings[i, 4] := FormatDateTime(fs.ShortTimeFormat + ' am/pm', SollDateTimes[i], fs);   // dont't use "t" - it does the hours wrong
    SollDateTimeStrings[i, 5] := FormatDateTime(fs.LongTimeFormat + ' am/pm', SollDateTimes[i], fs);
    SollDateTimeStrings[i, 6] := FormatDateTime(SpecialDateTimeFormat('dm', fs, false), SollDateTimes[i], fs);
    SollDateTimeStrings[i, 7] := FormatDateTime(SpecialDateTimeFormat('my', fs, false), SollDateTimes[i], fs);
    SollDateTimeStrings[i, 8] := FormatDateTime(SpecialDateTimeFormat('ms', fs, false), SollDateTimes[i], fs);
    SollDateTimeStrings[i, 9] := FormatDateTime('[h]:mm:ss', SollDateTimes[i], fs, [fdoInterval]);
  end;

  // Column width
  SollColWidths[0] := 20;  // characters based on width of "0" of default font
  SollColWidths[1] := 40;

  // Row heights
  SollRowHeights[0] := 1;  // Lines of default font
  SollRowHeights[1] := 2;
  SollRowHeights[2] := 4;

  // Cell borders
  SollBorders[0] := [];
  SollBorders[1] := [cbEast];
  SollBorders[2] := [cbSouth];
  SollBorders[3] := [cbWest];
  SollBorders[4] := [cbNorth];
  SollBorders[5] := [cbEast, cbSouth];
  SollBorders[6] := [cbEast, cbWest];
  SollBorders[7] := [cbEast, cbNorth];
  SollBorders[8] := [cbSouth, cbWest];
  SollBorders[9] := [cbSouth, cbNorth];
  SollBorders[10] := [cbWest, cbNorth];
  SollBorders[11] := [cbEast, cbSouth, cbWest];
  SollBorders[12] := [cbEast, cbSouth, cbNorth];
  SollBorders[13] := [cbSouth, cbWest, cbNorth];
  SollBorders[14] := [cbWest, cbNorth, cbEast];
  SollBorders[15] := [cbEast, cbSouth, cbWest, cbNorth];     // BIFF2/5 end here
  SollBorders[16] := [cbDiagUp];
  SollBorders[17] := [cbDiagDown];
  SollBorders[18] := [cbDiagUp, cbDiagDown];
  SollBorders[19] := [cbEast, cbSouth, cbWest, cbNorth, cbDiagUp, cbDiagDown];

  SollBorderLineStyles[0] := lsThin;
  SollBorderLineStyles[1] := lsMedium;
  SollBorderLineStyles[2] := lsThick;
  SollBorderLineStyles[3] := lsDashed;
  SollBorderLineStyles[4] := lsDotted;
  SollBorderLineStyles[5] := lsDouble;
  SollBorderLineStyles[6] := lsHair;

  SollBorderColors[0] := scBlue;
  SollBorderColors[1] := scRed;
  SollBorderColors[2] := scBlue;
  SollBorderColors[3] := scGray;
  SollBorderColors[4] := scSilver;
  SollBorderColors[5] := scMagenta;
end;

{ TSpreadWriteReadFormatTests }

procedure TSpreadWriteReadFormatTests.SetUp;
begin
  inherited SetUp;
  InitSollFmtData; //just for security: make sure the variables are reset to default
end;

procedure TSpreadWriteReadFormatTests.TearDown;
begin
  inherited TearDown;
end;


{ --- Number format tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_NumberFormats(AFormat: TsSpreadsheetFormat);
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  ActualString: String;
  Row, Col: Integer;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(FmtNumbersSheet);
    for Row := Low(SollNumbers) to High(SollNumbers) do
      for Col := ord(Low(SollNumberFormats)) to ord(High(SollNumberFormats)) do
      begin
        MyWorksheet.WriteNumber(Row, Col, SollNumbers[Row], SollNumberFormats[Col], SollNumberDecimals[Col]);
        ActualString := MyWorksheet.ReadAsUTF8Text(Row, Col);
        CheckEquals(SollNumberStrings[Row, Col], ActualString,
          'Test unsaved string mismatch cell ' + CellNotation(MyWorksheet,Row,Col));
      end;
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, FmtNumbersSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    for Row := Low(SollNumbers) to High(SollNumbers) do
      for Col := Low(SollNumberFormats) to High(SollNumberFormats) do
      begin
        ActualString := MyWorkSheet.ReadAsUTF8Text(Row,Col);
        CheckEquals(SollNumberStrings[Row,Col], ActualString,
          'Test saved string mismatch cell '+CellNotation(MyWorkSheet,Row,Col));
      end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_NumberFormats;
begin
  TestWriteRead_NumberFormats(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_NumberFormats;
begin
  TestWriteRead_NumberFormats(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_NumberFormats;
begin
  TestWriteRead_NumberFormats(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_NumberFormats;
begin
  TestWriteRead_NumberFormats(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_NumberFormats;
begin
  TestWriteRead_NumberFormats(sfOOXML);
end;


{ --- Date/time formats --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_DateTimeFormats(AFormat: TsSpreadsheetFormat);
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  ActualString: String;
  Row,Col: Integer;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorksheet := MyWorkbook.AddWorksheet(FmtDateTimesSheet);
    for Row := Low(SollDateTimes) to High(SollDateTimes) do
      for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do
      begin
        if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfCustom, nfTimeInterval]) then
          Continue;  // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2
        MyWorksheet.WriteDateTime(Row, Col, SollDateTimes[Row], SollDateTimeFormats[Col], SollDateTimeFormatStrings[Col]);
        ActualString := MyWorksheet.ReadAsUTF8Text(Row, Col);
        CheckEquals(
          Lowercase(SollDateTimeStrings[Row, Col]),
          Lowercase(ActualString),
          'Test unsaved string mismatch cell ' + CellNotation(MyWorksheet,Row,Col)
        );
      end;
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkbook, FmtDateTimesSheet);
    if MyWorksheet = nil then
      fail('Error in test code. Failed to get named worksheet');
    for Row := Low(SollDateTimes) to High(SollDateTimes) do
      for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do
      begin
        if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfCustom, nfTimeInterval]) then
          Continue;  // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2
        ActualString := MyWorksheet.ReadAsUTF8Text(Row,Col);
        CheckEquals(
          Lowercase(SollDateTimeStrings[Row, Col]),
          Lowercase(ActualString),
          'Test saved string mismatch cell '+CellNotation(MyWorksheet,Row,Col)
        );
      end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_DateTimeFormats;
begin
  TestWriteRead_DateTimeFormats(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_DateTimeFormats;
begin
  TestWriteRead_DateTimeFormats(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_DateTimeFormats;
begin
  TestWriteRead_DateTimeFormats(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_DateTimeFormats;
begin
  TestWriteRead_DateTimeFormats(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_DateTimeFormats;
begin
  TestWriteRead_DateTimeFormats(sfOOXML);
end;

{ --- Alignment tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_Alignment(AFormat: TsSpreadsheetFormat);
const
  HORALIGN_TEXT: Array[TsHorAlignment] of String = ('haDefault', 'haLeft', 'haCenter', 'haRight');
  VERTALIGN_TEXT: Array[TsVertAlignment] of String = ('vaDefault', 'vaTop', 'vaCenter', 'vaBottom');
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  horAlign: TsHorAlignment;
  vertAlign: TsVertAlignment;
  row, col: Integer;
  MyCell: PCell;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values: HorAlignments along rows, VertAlignments along columns
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(AlignmentSheet);

    col := 0;
    for horAlign in TsHorAlignment do
    begin
      row := 0;
      if AFormat = sfExcel2 then
      begin
        // BIFF2 can only do horizontal alignment --> no need for vertical alignment.
        MyWorksheet.WriteUTF8Text(row, col, HORALIGN_TEXT[horAlign]);
        MyWorksheet.WriteHorAlignment(row, col, horAlign);
        MyCell := MyWorksheet.FindCell(row, col);
        if MyCell = nil then
          fail('Error in test code. Failed to get cell.');
        CheckEquals(
          GetEnumName(TypeInfo(TsHorAlignment), Integer(horAlign)),
          GetEnumName(TypeInfo(TsHorAlignment), Integer(MyCell^.HorAlignment)),
          'Test unsaved horizontal alignment, cell ' + CellNotation(MyWorksheet,0,0)
        );
      end
      else
        for vertAlign in TsVertAlignment do
        begin
          MyWorksheet.WriteUTF8Text(row, col, HORALIGN_TEXT[horAlign]+'/'+VERTALIGN_TEXT[vertAlign]);
          MyWorksheet.WriteHorAlignment(row, col, horAlign);
          MyWorksheet.WriteVertAlignment(row, col, vertAlign);
          MyCell := MyWorksheet.FindCell(row, col);
          if MyCell = nil then
            fail('Error in test code. Failed to get cell.');
          CheckEquals(
            GetEnumName(TypeInfo(TsVertAlignment), Integer(vertAlign)),
            GetEnumName(TypeInfo(TsVertAlignment), Integer(MyCell^.VertAlignment)),
            'Test unsaved vertical alignment, cell ' + CellNotation(MyWorksheet,0,0)
          );
          CheckEquals(
            GetEnumName(TypeInfo(TsHorAlignment), Integer(horAlign)),
            GetEnumName(TypeInfo(TsHorAlignment), Integer(MyCell^.HorAlignment)),
            'Test unsaved horizontal alignment, cell ' + CellNotation(MyWorksheet,0,0)
          );
          inc(row);
        end;
      inc(col);
    end;
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then begin
      MyWorksheet := MyWorkbook.GetFirstWorksheet;
      if MyWorksheet=nil then
        fail('Error in test code. Failed to get named worksheet');
      row := 0;
      for col :=0 to MyWorksheet.GetLastColIndex do
      begin
        MyCell := MyWorksheet.FindCell(row, col);
        if MyCell = nil then
          fail('Error in test code. Failed to get cell.');
        horAlign := TsHorAlignment(col);
        CheckEquals(
          GetEnumName(TypeInfo(TsHorAlignment), Integer(horAlign)),
          GetEnumName(TypeInfo(TsHorAlignment), Integer(MyCell^.HorAlignment)),
          'Test saved horizontal alignment mismatch, cell ' + CellNotation(MyWorksheet,row, col)
        );
      end
    end
    else begin
      MyWorksheet := GetWorksheetByName(MyWorkBook, AlignmentSheet);
      if MyWorksheet=nil then
        fail('Error in test code. Failed to get named worksheet');
      for col :=0 to MyWorksheet.GetLastColIndex do
        for row := 0 to MyWorksheet.GetlastRowIndex do
        begin
          MyCell := MyWorksheet.FindCell(row, col);
          if MyCell = nil then
            fail('Error in test code. Failed to get cell.');
          vertAlign := TsVertAlignment(row);
          if (vertAlign = vaDefault) and (AFormat in [sfExcel5, sfExcel8]) then
            vertAlign := vaBottom;
          CheckEquals(
            GetEnumName(TypeInfo(TsVertAlignment), Integer(vertAlign)),
            GetEnumName(TypeInfo(TsVertAlignment), Integer(MyCell^.VertAlignment)),
            'Test saved vertical alignment mismatch, cell ' + CellNotation(MyWorksheet,row,col)
          );
          horAlign := TsHorAlignment(col);
          CheckEquals(
            GetEnumName(TypeInfo(TsHorAlignment), Integer(horAlign)),
            GetEnumName(TypeInfo(TsHorAlignment), Integer(MyCell^.HorAlignment)),
            'Test saved horizontal alignment mismatch, cell ' + CellNotation(MyWorksheet,row,col)
          );
        end;
    end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_Alignment;
begin
  TestWriteRead_Alignment(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_Alignment;
begin
  TestWriteRead_Alignment(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_Alignment;
begin
  TestWriteRead_Alignment(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_Alignment;
begin
  TestWriteRead_Alignment(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_Alignment;
begin
  TestWriteRead_Alignment(sfOOXML);
end;


{ --- Border on/off tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_Border(AFormat: TsSpreadsheetFormat);
const
  row = 0;
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  MyCell: PCell;
  col, maxCol: Integer;
  TempFile: string; //write xls/xml to this file and read back from it

  function GetBordersAsText(ABorders: TsCellBorders): String;
  var
    cb: TsCellBorder;
  begin
    Result := '';
    for cb in ABorders do
      if Result = '' then
        Result := GetEnumName(TypeInfo(TsCellBorder), ord(cb))
      else
        Result := Result + ', ' + GetEnumName(TypeInfo(TsCellBorder), ord(cb));
  end;

begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(BordersSheet);
    if AFormat in [sfExcel2, sfExcel5] then
      maxCol := 15   // no diagonal border support in BIFF2 and BIFF5
    else
      maxCol := High(SollBorders);
    for col := Low(SollBorders) to maxCol do
    begin
      MyWorksheet.WriteUsedFormatting(row, col, [uffBorder]);
      MyCell := MyWorksheet.GetCell(row, col);
      Include(MyCell^.UsedFormattingFields, uffBorder);
      MyCell^.Border := SollBorders[col];
    end;

    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, BordersSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    for col := 0 to MyWorksheet.GetLastColIndex do
    begin
      MyCell := MyWorksheet.FindCell(row, col);
      if MyCell = nil then
        fail('Error in test code. Failed to get cell');
      CheckEquals(
        GetBordersAsText(SollBorders[col]),
        GetBordersAsText(MyCell^.Border),
        'Test saved border mismatch, cell ' + CellNotation(MyWorksheet, row, col)
      );
    end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_Border;
begin
  TestWriteRead_Border(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_Border;
begin
  TestWriteRead_Border(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_Border;
begin
  TestWriteRead_Border(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_Border;
begin
  TestWriteRead_Border(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_Border;
begin
  TestWriteRead_Border(sfOOXML);
end;


{ --- BorderStyle tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_BorderStyles(AFormat: TsSpreadsheetFormat);
{ This test paints 10x10 cells with all borders, each separated by an empty
  column and an empty row. The border style varies from border to border
  according to the line styles defined in SollBorderStyles. At first, all border
  lines use the first color in SollBorderColors. When all BorderStyles are used
  the next color is taken, etc. }
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  MyCell: PCell;
  row, col: Integer;
  b: TsCellBorder;
  expected: Integer;
  current: Integer;
  TempFile: string; //write xls/xml to this file and read back from it
  c, ls: Integer;
  borders: TsCellBorders;
  diagUp_ls: Integer;
  diagUp_clr: integer;
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(BordersSheet);

    borders := [cbNorth, cbSouth, cbEast, cbWest];
    if AFormat in [sfExcel8, sfOpenDocument, sfOOXML] then
      borders := borders + [cbDiagUp, cbDiagDown];

    c := 0;
    ls := 0;
    for row := 1 to 10 do
    begin
      for col := 1 to 10 do
      begin
        MyWorksheet.WriteBorders(row*2, col*2, borders);
        for b in borders do
        begin
          MyWorksheet.WriteBorderLineStyle(row*2, col*2, b, SollBorderLineStyles[ls]);
          MyWorksheet.WriteBorderColor(row*2, col*2, b, SollBorderColors[c]);
          inc(ls);
          if ls > High(SollBorderLineStyles) then
          begin
            ls := 0;
            inc(c);
            if c > High(SollBorderColors) then
              c := 0;
          end;
        end;
      end;
    end;
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, BordersSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    c := 0;
    ls := 0;
    for row := 1 to 10 do
    begin
      for col := 1 to 10 do
      begin
        MyCell := MyWorksheet.FindCell(row*2, col*2);
        if myCell = nil then
          fail('Error in test code. Failed to get cell.');
        for b in borders do
        begin
          current := ord(MyCell^.BorderStyles[b].LineStyle);
          // In Excel both diagonals have the same line style. The reader picks
          // the line style of the "diagonal-up" border. We use this as expected
          // value in the "diagonal-down" case.
          expected := ord(SollBorderLineStyles[ls]);
          if AFormat in [sfExcel8, sfOOXML] then
            case b of
              cbDiagUp  : diagUp_ls := expected;
              cbDiagDown: expected := diagUp_ls;
            end;
          CheckEquals(expected, current,
            'Test saved border line style mismatch, cell ' + CellNotation(MyWorksheet, row*2, col*2));
          current := MyCell^.BorderStyles[b].Color;
          expected := SollBorderColors[c];
          // In Excel both diagonals have the same line color. The reader picks
          // the color of the "diagonal-up" border. We use this as expected value
          // in the "diagonal-down" case.
          if AFormat in [sfExcel8, sfOOXML] then
            case b of
              cbDiagUp  : diagUp_clr := expected;
              cbDiagDown: expected := diagUp_clr;
            end;
          CheckEquals(expected, current,
            'Test saved border color mismatch, cell ' + CellNotation(MyWorksheet, row*2, col*2));
          inc(ls);
          if ls > High(SollBorderLineStyles) then begin
            ls := 0;
            inc(c);
            if c > High(SollBorderColors) then
              c := 0;
          end;
        end;
      end;
    end;

  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_BorderStyles;
begin
  TestWriteRead_BorderStyles(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_BorderStyles;
begin
  TestWriteRead_BorderStyles(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_BorderStyles;
begin
  TestWriteRead_BorderStyles(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_BorderStyles;
begin
  TestWriteRead_BorderStyles(sfOOXML);
end;


{ --- Column widths tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_ColWidths(AFormat: TsSpreadsheetFormat);
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  ActualColWidth: Single;
  Col: Integer;
  lpCol: PCol;
  lCol: TCol;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(ColWidthSheet);
    for Col := Low(SollColWidths) to High(SollColWidths) do
    begin
      lCol.Width := SollColWidths[Col];
      //MyWorksheet.WriteNumber(0, Col, 1);
      MyWorksheet.WriteColInfo(Col, lCol);
    end;
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet, as biff8
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, ColWidthSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    for Col := Low(SollColWidths) to High(SollColWidths) do
    begin
      lpCol := MyWorksheet.GetCol(Col);
      if lpCol = nil then
        fail('Error in test code. Failed to return saved column width');
      ActualColWidth := lpCol^.Width;
      if abs(SollColWidths[Col] - ActualColWidth) > 1E-2 then   // take rounding errors into account
        CheckEquals(SollColWidths[Col], ActualColWidth,
          'Test saved colwidth mismatch, column '+ColNotation(MyWorkSheet,Col));
    end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_ColWidths;
begin
  TestWriteRead_ColWidths(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_ColWidths;
begin
  TestWriteRead_ColWidths(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_ColWidths;
begin
  TestWriteRead_ColWidths(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_ColWidths;
begin
  TestWriteRead_ColWidths(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_ColWidths;
begin
  TestWriteRead_ColWidths(sfOOXML);
end;


{ --- Row height tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_RowHeights(AFormat: TsSpreadsheetFormat);
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  ActualRowHeight: Single;
  Row: Integer;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(RowHeightSheet);
    for Row := Low(SollRowHeights) to High(SollRowHeights) do
      MyWorksheet.WriteRowHeight(Row, SollRowHeights[Row]);
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, RowHeightSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    for Row := Low(SollRowHeights) to High(SollRowHeights) do
    begin
      ActualRowHeight := MyWorksheet.GetRowHeight(Row);
      // Take care of rounding errors - due to missing details of calculation
      // they can be quite large...
      if abs(ActualRowHeight - SollRowHeights[Row]) > 1e-2 then
        CheckEquals(SollRowHeights[Row], ActualRowHeight,
          'Test saved row height mismatch, row '+RowNotation(MyWorkSheet,Row));
    end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_RowHeights;
begin
  TestWriteRead_RowHeights(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_RowHeights;
begin
  TestWriteRead_RowHeights(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_RowHeights;
begin
  TestWriteRead_RowHeights(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_RowHeights;
begin
  TestWriteRead_RowHeights(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_RowHeights;
begin
  TestWriteRead_RowHeights(sfOOXML);
end;


{ --- Text rotation tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_TextRotation(AFormat: TsSpreadsheetFormat);
const
  col = 0;
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  MyCell: PCell;
  tr: TsTextRotation;
  row: Integer;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(TextRotationSheet);
    for tr := Low(TsTextRotation) to High(TsTextRotation) do
    begin
      row := ord(tr);
      MyWorksheet.WriteTextRotation(row, col, tr);
      MyCell := MyWorksheet.GetCell(row, col);
      CheckEquals(
        GetEnumName(TypeInfo(TsTextRotation), ord(tr)),
        GetEnumName(TypeInfo(TsTextRotation), ord(MyCell^.TextRotation)),
        'Test unsaved textrotation mismatch, cell ' + CellNotation(MyWorksheet, row, col));
    end;
    TempFile:=NewTempFile;
  finally
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, TextRotationSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    for row := 0 to MyWorksheet.GetLastRowIndex do
    begin
      MyCell := MyWorksheet.FindCell(row, col);
      if MyCell = nil then
        fail('Error in test code. Failed to get cell');
      tr := MyCell^.TextRotation;
      CheckEquals(
        GetEnumName(TypeInfo(TsTextRotation), ord(TsTextRotation(row))),
        GetEnumName(TypeInfo(TsTextRotation), ord(MyCell^.TextRotation)),
        'Test saved textrotation mismatch, cell ' + CellNotation(MyWorksheet, row, col));
    end;
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_TextRotation;
begin
  TestWriteRead_TextRotation(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_TextRotation;
begin
  TestWriteRead_TextRotation(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_TextRotation;
begin
  TestWriteRead_TextRotation(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_TextRotation;
begin
  TestWriteRead_TextRotation(sfOOXML);
end;


{ --- Wordwrap tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_WordWrap(AFormat: TsSpreadsheetFormat);
const
  LONGTEXT = 'This is a very, very, very, very long text.';
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  MyCell: PCell;
  TempFile: string; //write xls/xml to this file and read back from it
begin
  {// Not needed: use workbook.writetofile with overwrite=true
  if fileexists(TempFile) then
    DeleteFile(TempFile);
  }
  // Write out all test values:
  // Cell A1 is word-wrapped, Cell B1 is NOT word-wrapped
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkSheet:= MyWorkBook.AddWorksheet(WordwrapSheet);
    MyWorksheet.WriteUTF8Text(0, 0, LONGTEXT);
    MyWorksheet.WriteUsedFormatting(0, 0, [uffWordwrap]);
    MyCell := MyWorksheet.FindCell(0, 0);
    if MyCell = nil then
      fail('Error in test code. Failed to get word-wrapped cell.');
    CheckEquals(true, (uffWordWrap in MyCell^.UsedFormattingFields), 'Test unsaved word wrap mismatch cell ' + CellNotation(MyWorksheet,0,0));
    MyWorksheet.WriteUTF8Text(1, 0, LONGTEXT);
    MyWorksheet.WriteUsedFormatting(1, 0, []);
    MyCell := MyWorksheet.FindCell(1, 0);
    if MyCell = nil then
      fail('Error in test code. Failed to get word-wrapped cell.');
    CheckEquals(false, (uffWordWrap in MyCell^.UsedFormattingFields), 'Test unsaved non-wrapped cell mismatch, cell ' + CellNotation(MyWorksheet,0,0));
    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet, as biff8
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, WordwrapSheet);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet');
    MyCell := MyWorksheet.FindCell(0, 0);
    if MyCell = nil then
      fail('Error in test code. Failed to get word-wrapped cell.');
    CheckEquals(true, (uffWordWrap in MyCell^.UsedFormattingFields),
      'Failed to return correct word-wrap flag, cell ' + CellNotation(MyWorksheet,0,0));
    MyCell := MyWorksheet.FindCell(1, 0);
    if MyCell = nil then
      fail('Error in test code. Failed to get non-wrapped cell.');
    CheckEquals(false, (uffWordWrap in MyCell^.UsedFormattingFields),
      'Failed to return correct word-wrap flag, cell ' + CellNotation(MyWorksheet,0,0));
  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_Wordwrap;
begin
  TestWriteRead_Wordwrap(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_Wordwrap;
begin
  TestWriteRead_Wordwrap(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_Wordwrap;
begin
  TestWriteRead_Wordwrap(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_Wordwrap;
begin
  TestWriteRead_Wordwrap(sfOOXML);
end;


{ --- Merged tests --- }

procedure TSpreadWriteReadFormatTests.TestWriteRead_MergedCells(AFormat: TsSpreadsheetFormat);
const
  TEST_RANGES: Array[0..3] of string = ('A1:B1', 'E1:G5', 'H1:H5', 'L2:M4');
  SHEETNAME1 = 'Sheet1';
  SHEETNAME2 = 'Sheet2';
  SHEETNAME3 = 'Sheet3';
  CELL_TEXT = 'Lazarus';
var
  MyWorksheet: TsWorksheet;
  MyWorkbook: TsWorkbook;
  cell: PCell;
  TempFile: string; //write xls/xml to this file and read back from it
  r1, c1, r2, c2: Cardinal;
  r, c: Cardinal;
  actual, expected: String;
  i: Integer;
begin
  MyWorkbook := TsWorkbook.Create;
  try
    // 1st sheet: merged ranges with text
    MyWorksheet:= MyWorkBook.AddWorksheet(SHEETNAME1);
    for i:=0 to High(TEST_RANGES) do
    begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      Myworksheet.WriteUTF8Text(r1, c1, CELL_TEXT);
      Myworksheet.MergeCells(r1, c1, r2, c2);
    end;

    // 2nd sheet: merged ranges, empty
    Myworksheet := MyWorkbook.AddWorksheet(SHEETNAME2);
    for i:=0 to High(TEST_RANGES) do
    begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      Myworksheet.MergeCells(r1, c1, r2, c2);
    end;

    // 3rd sheet: merged ranges, with text, then unmerge all
    MyWorksheet:= MyWorkBook.AddWorksheet(SHEETNAME3);
    for i:=0 to High(TEST_RANGES) do
    begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      Myworksheet.WriteUTF8Text(r1, c1, CELL_TEXT);
      Myworksheet.MergeCells(r1, c1, r2, c2);
      Myworksheet.UnmergeCells(r1, c1);
    end;

    TempFile:=NewTempFile;
    MyWorkBook.WriteToFile(TempFile, AFormat, true);
  finally
    MyWorkbook.Free;
  end;

  // Open the spreadsheet
  MyWorkbook := TsWorkbook.Create;
  try
    MyWorkbook.ReadFromFile(TempFile, AFormat);

    // 1st sheet: merged cells with text
    if AFormat = sfExcel2 then
      MyWorksheet := MyWorkbook.GetFirstWorksheet
    else
      MyWorksheet := GetWorksheetByName(MyWorkBook, SHEETNAME1);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet ' + SHEETNAME1);
    for i:=0 to High(TEST_RANGES) do begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      cell := MyWorksheet.FindCell(r1, c1);
      if MyWorksheet.IsMergeBase(cell) then begin
        MyWorksheet.FindMergedRange(cell, r1, c1, r2, c2);
        actual := GetCellRangeString(r1, c1, r2, c2);
        expected := TEST_RANGES[i];
        if AFormat in [sfExcel2, sfExcel5] then
          CheckNotEquals(expected, actual, 'No merged cells expected, ' + expected)
        else
          CheckEquals(expected, actual, 'Merged cell range mismatch, ' + expected);
      end else
      if not (AFormat in [sfExcel2, sfExcel5]) then
        fail('Unmerged cell found, ' + CellNotation(MyWorksheet, r1, c1));
      CheckEquals(CELL_TEXT, MyWorksheet.ReadAsUTF8Text(cell),
        'Merged cell content mismatch, cell '+ CellNotation(MyWorksheet, r1, c1));
    end;

    if AFormat = sfExcel2 then
      exit;  // only 1 page in Excel2

    // 2nd sheet: merged empty cells
    MyWorksheet := GetWorksheetByName(MyWorkBook, SHEETNAME2);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet' + SHEETNAME2);
    for i:=0 to High(TEST_RANGES) do begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      cell := MyWorksheet.FindCell(r1, c1);
      if MyWorksheet.IsMergeBase(cell) then begin
        MyWorksheet.FindMergedRange(cell, r1, c1, r2, c2);
        actual := GetCellRangeString(r1, c1, r2, c2);
        expected := TEST_RANGES[i];
        if AFormat = sfExcel5 then
          CheckNotEquals(expected, actual, 'Merged cells found in Excel5, ' + expected)
        else
          CheckEquals(expected, actual, 'Merged cell range mismatch, ' + expected);
      end else
      if AFormat <> sfExcel5 then
        fail('Unmerged cell found, ' + CellNotation(MyWorksheet, r1, c1));
      CheckEquals('', MyWorksheet.ReadAsUTF8Text(cell),
        'Merged cell content mismatch, cell '+CellNotation(MyWorksheet, r1, c1));
    end;

    // 3rd sheet: merged & unmerged cells
    MyWorksheet := GetWorksheetByName(MyWorkBook, SHEETNAME3);
    if MyWorksheet=nil then
      fail('Error in test code. Failed to get named worksheet' + SHEETNAME3);
    for i:=0 to High(TEST_RANGES) do begin
      ParseCellRangeString(TEST_RANGES[i], r1, c1, r2, c2);
      cell := MyWorksheet.FindCell(r1, c1);
      if MyWorksheet.IsMergeBase(cell) then
        fail('Unmerged cell expected, cell ' + CellNotation(MyWorksheet, r1, c1));
      CheckEquals(CELL_TEXT, MyWorksheet.ReadAsUTF8Text(cell),
        'Merged/unmerged cell content mismatch, cell '+CellNotation(MyWorksheet, r1, c1));
    end;

  finally
    MyWorkbook.Free;
    DeleteFile(TempFile);
  end;
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_MergedCells;
begin
  TestWriteRead_MergedCells(sfExcel2);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_MergedCells;
begin
  TestWriteRead_MergedCells(sfExcel5);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_MergedCells;
begin
  TestWriteRead_MergedCells(sfExcel8);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_MergedCells;
begin
  TestWriteRead_MergedCells(sfOpenDocument);
end;

procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_MergedCells;
begin
  TestWriteRead_MergedCells(sfOOXML);
end;



initialization
  RegisterTest(TSpreadWriteReadFormatTests);
  InitSollFmtData;

end.

