From e191dd00e5f28c187bc052cad834890907da1117 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 23 Jul 2020 22:25:24 +0000 Subject: [PATCH] fpspreadsheet: Support conditional icon set format in worksheet, and in XLSX and ODS file access. Add unit tests for it. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7558 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../demo_conditional_formatting.pas | 8 +- .../source/common/fpsconditionalformat.pas | 260 ++++++++- .../source/common/fpsopendocument.pas | 104 +++- .../source/common/fpspreadsheet.pas | 18 + .../source/common/fpspreadsheet_cf.inc | 66 +++ .../fpspreadsheet/source/common/xlsxooxml.pas | 497 +++++++++++------- .../tests/conditionalformattests.pas | 195 +++++++ .../fpspreadsheet/tests/spreadtestgui.lpi | 6 +- 8 files changed, 954 insertions(+), 200 deletions(-) diff --git a/components/fpspreadsheet/examples/other/conditional_formatting/demo_conditional_formatting.pas b/components/fpspreadsheet/examples/other/conditional_formatting/demo_conditional_formatting.pas index 98da5107e..e6291cc3e 100644 --- a/components/fpspreadsheet/examples/other/conditional_formatting/demo_conditional_formatting.pas +++ b/components/fpspreadsheet/examples/other/conditional_formatting/demo_conditional_formatting.pas @@ -26,7 +26,7 @@ begin sh.WriteText(0, 2, 'Test values'); row := 2; - for i := row to row+30 do + for i := row to row+33 do begin sh.WriteNumber(i, 2, 1.0); sh.WriteNumber(i, 3, 2.0); @@ -324,6 +324,12 @@ begin sh.WriteText(row, 1, 'yellow -> red'); sh.WriteColorRange(Range(Row, 2, row, 12), scYellow, scRed); + // Icon sets + inc(row); + sh.WriteText(row, 0, 'IconSet'); + sh.WriteText(row, 1, '3 flags'); + sh.WriteIconSet(Range(Row, 2, row, 12), is3Flags); + { ------ Save workbook to file-------------------------------------------- } wb.WriteToFile('test.xlsx', true); wb.WriteToFile('test.ods', true); diff --git a/components/fpspreadsheet/source/common/fpsconditionalformat.pas b/components/fpspreadsheet/source/common/fpsconditionalformat.pas index 65c1f01dd..271ca0ea3 100644 --- a/components/fpspreadsheet/source/common/fpsconditionalformat.pas +++ b/components/fpspreadsheet/source/common/fpsconditionalformat.pas @@ -68,6 +68,41 @@ type procedure Assign(ASource: TsCFRule); override; end; + { Icon sets } + TsCFIconSet = ( + is3Arrows, is3ArrowsGray, is3Flags, + is3TrafficLights1, // x14 in xlsx + is3TrafficLights2, is3Signs, is3Symbols, is3Symbols2, + is3Smilies, is3Stars, is3Triangles, is3ColorSmilies, // need x14 in xlsx + is4Arrows, is4ArrowsGray, is4RedToBlack, is4Rating, + is4TrafficLights, // not in ODS + is5Arrows, is5ArrowsGray, is5Rating, is5Quarters, + is5Boxes // needs x14 in Excel + ); + TsCFIconSetRule = class(TsCFRule) + private + FIconSet: TsCFIconSet; + FReverse: Boolean; + FShowValue: Boolean; + FValueKinds: array of TsCFValuekind; + FValues: array of double; + function GetIconCount: Integer; + function GetValueKinds(AIndex: Integer): TsCFValueKind; + function GetValues(AIndex: Integer): Double; + procedure SetIconSet(AValue: TsCFIconSet); + procedure SetValueKinds(AIndex: Integer; AKind: TsCFValueKind); + procedure SetValues(AIndex: Integer; AValue: Double); + public + constructor Create; + procedure Assign(ASource: TsCFRule); override; + property IconSet: TsCFIconSet read FIconSet write SetIconSet; + property IconCount: Integer read GetIconCount; + property Values[AIndex: Integer]: Double read GetValues write SetValues; + property ValueKinds[AIndex: Integer]: TsCFValueKind read GetValueKinds write SetValueKinds; + property Reverse: Boolean read FReverse write FReverse; + property ShowValue: Boolean read FShowValue write FShowValue; + end; + { Rules } TsCFRules = class(TFPObjectList) private @@ -127,17 +162,44 @@ type ABarColor: TsColor; AStartKind: TsCFValueKind; AStartValue: Double; AEndKind: TsCFValueKind; AEndValue: Double): Integer; overload; + function AddIconSetRule(ASheet: TsBasicWorksheet; ARange: TsCellRange; + AIconSet: TsCFIconSet; AHideValue: Boolean = false; AReverse: Boolean = false): Integer; overload; + function AddIconSetRule(ASheet: TsBasicWorksheet; ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; overload; + function AddIconSetRule(ASheet: TsBasicWorksheet; ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; overload; + function AddIconSetRule(ASheet: TsBasicWorksheet; ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AValueKind4: TsCFValueKind; AValue4: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; overload; + procedure Delete(AIndex: Integer); function Find(ASheet: TsBasicWorksheet; ARange: TsCellRange): Integer; end; + function GetCFIconCount(AIconSet: TsCFIconSet): Integer; implementation uses - Math, + Math, TypInfo, fpSpreadsheet; +function GetCFIconCount(AIconSet: TsCFIconSet): Integer; +var + s: String; +begin + s := GetEnumName(TypeInfo(TsCFIconSet), integer(AIconSet)); + Result := ord(s[3]) - ord('0'); +end; + procedure TsCFCellRule.Assign(ASource: TsCFRule); begin if ASource is TsCFCellRule then @@ -164,7 +226,7 @@ begin begin // end else - raise Exception.Create('Source cannot be assigned to TCVDataBarRule'); + raise Exception.Create('Source cannot be assigned to TsCFDataBarRule'); end; constructor TsCFColorRangeRule.Create; @@ -194,7 +256,7 @@ begin CenterColor := TsCFColorRangeRule(ASource).CenterColor; EndColor := TsCFColorRangeRule(ASource).EndColor; end else - raise Exception.Create('Source cannot be assigned to TCVDataBarRule'); + raise Exception.Create('Source cannot be assigned to TsCFColorRangeRule'); end; procedure TsCFColorRangeRule.SetupCenter(AColor: TsColor; @@ -222,8 +284,89 @@ begin end; +{ TsCFIconSetRule } -{ TCFRule } +constructor TsCFIconSetRule.Create; +begin + FIconSet := is3Arrows; + SetLength(FValues, 2); + Setlength(FValueKinds, 2); + FValues[0] := 33; + FValues[1] := 66; + FValueKinds[0] := vkPercent; + FValueKinds[1] := vkPercent; + FShowValue := true; + FReverse := false; +end; + +procedure TsCFIconSetRule.Assign(ASource: TsCFRule); +var + i: Integer; +begin + if ASource is TsCFIconSetRule then + begin + SetIconSet(TsCFIconSetRule(ASource).IconSet); + for i := 0 to High(FValues) do + FValues[i] := TsCFIconSetRule(ASource).Values[i]; + for i := 0 to High(FValueKinds) do + FValueKinds[i] := TsCFIconSetRule(ASource).ValueKinds[i]; + FShowValue := TsCFIconSetRule(ASource).ShowValue; + FReverse := TsCFIconSetRule(ASource).Reverse; + end else + raise Exception.Create('Source cannot be assigned to TsCFIconSetRule'); +end; + +function TsCFIconSetRule.GetIconCount: Integer; +begin + Result := Length(FValues) + 1; +end; + +function TsCFIconSetRule.GetValueKinds(AIndex: Integer): TsCFValueKind; +begin + Result := FValueKinds[AIndex]; +end; + +function TsCFIconSetRule.GetValues(AIndex: Integer): Double; +begin + Result := FValues[AIndex]; +end; + +procedure TsCFIconSetRule.SetIconSet(AValue: TsCFIconSet); +var + s: String; + i, n: Integer; +begin + if AValue = FIconSet then exit; + + FIconSet := AValue; + + s := GetEnumName(TypeInfo(TsCFIconSet), integer(AValue)); + n := Ord(s[3]) - ord('0'); + SetLength(FValues, n - 1); + for i := 0 to High(FValues) do + FValues[i] := (i + 1) * 100 div n; + SetLength(FValueKinds, n - 1); + for i := 0 to High(FValueKinds) do + FValueKinds[i] := vkPercent; + + // value index + // (min) 0 1 2 (max) + // |---------|---------|---------|----------| + // icon0 icon1 icon2 icon3 +end; + +procedure TsCFIconSetRule.SetValueKinds(AIndex: Integer; AKind: TsCFValueKind); +begin + FValueKinds[AIndex] := AKind; +end; + +procedure TsCFIconSetRule.SetValues(AIndex: Integer; AValue: Double); +begin + FValues[AIndex] := AValue; +end; + + +{ TsCFRule } function TsCFRules.GetItem(AIndex: Integer): TsCFRule; begin @@ -424,6 +567,115 @@ begin end; +function TsConditionalFormatList.AddIconSetRule(ASheet: TsBasicWorksheet; + ARange: TsCellRange; AIconSet: TsCFIconSet; AHideValue: Boolean = false; + AReverse: Boolean = false): Integer; +var + rule: TsCFIconSetRule; + i, n: Integer; +begin + rule := TsCFIconSetRule.Create; + rule.IconSet := AIconset; + n := rule.IconCount; + for i := 0 to n - 2 do + begin + rule.ValueKinds[i] := vkPercent; + rule.Values[i] := ((i+1) * 100) div n + end; + rule.ShowValue := not AHideValue; + rule.Reverse := AReverse; + Result := AddRule(ASheet, ARange, rule); +end; + +{ IconSet conditional format for 3 icons, ie. 2 values } +function TsConditionalFormatList.AddIconSetRule(ASheet: TsBasicWorksheet; + ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; +var + rule: TsCFIconSetRule; + n: Integer; +begin + rule := TsCFIconSetRule.Create; + rule.IconSet := AIconset; + n := rule.IconCount; + if n <> 3 then begin + rule.Free; + Result := -1; + exit; + end; + + rule.ValueKinds[0] := AValueKind1; rule.Values[0] := AValue1; + rule.ValueKinds[1] := AValueKind2; rule.Values[1] := AValue2; + + rule.ShowValue := not AHideValue; + rule.Reverse := AReverse; + + Result := AddRule(ASheet, ARange, rule); +end; + +{ IconSet conditional format for 4 icons, i.e. 3 values } +function TsConditionalFormatList.AddIconSetRule(ASheet: TsBasicWorksheet; + ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; +var + rule: TsCFIconSetRule; + n: Integer; +begin + rule := TsCFIconSetRule.Create; + rule.IconSet := AIconset; + n := rule.IconCount; + if n <> 4 then begin + rule.Free; + Result := -1; + exit; + end; + + rule.ValueKinds[0] := AValueKind1; rule.Values[0] := AValue1; + rule.ValueKinds[1] := AValueKind2; rule.Values[1] := AValue2; + rule.ValueKinds[2] := AValueKind3; rule.Values[2] := AValue3; + + rule.ShowValue := not AHideValue; + rule.Reverse := AReverse; + + Result := AddRule(ASheet, ARange, rule); +end; + +{ Iconset conditional format for 5 icons, i.e. 4 values } +function TsConditionalFormatList.AddIconSetRule(ASheet: TsBasicWorksheet; + ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; AValueKind4: TsCFValueKind; AValue4: Double; + AHideValue: Boolean = false; AReverse: Boolean = false): Integer; +var + rule: TsCFIconSetRule; + n: Integer; +begin + rule := TsCFIconSetRule.Create; + rule.IconSet := AIconset; + n := rule.IconCount; + if n <> 5 then begin + rule.Free; + Result := -1; + exit; + end; + + rule.ValueKinds[0] := AValueKind1; rule.Values[0] := AValue1; + rule.ValueKinds[1] := AValueKind2; rule.Values[1] := AValue2; + rule.ValueKinds[2] := AValueKind3; rule.Values[2] := AValue3; + rule.ValueKinds[3] := AValueKind4; rule.Values[3] := AValue4; + + rule.ShowValue := not AHideValue; + rule.Reverse := AReverse; + + Result := AddRule(ASheet, ARange, rule); +end; + + {@@ ---------------------------------------------------------------------------- Deletes the conditional format at the given index from the list. Iterates also through all cell in the range of the CF and removess the diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index ec94b640c..9951a6361 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -118,6 +118,7 @@ type procedure ReadCFCellFormat(ANode: TDOMNode; ASheet: TsBasicWorksheet; ARange: TsCellRange); procedure ReadCFColorScale(ANode: TDOMNode; ASheet: TsBasicWorksheet; ARange: TsCellRange); procedure ReadCFDataBars(ANode: TDOMNode; ASheet: TsBasicWorksheet; ARange: TsCellRange); + procedure ReadCFIconSet(ANode: TDOMNode; ASheet: TsBasicWorksheet; ARange: TsCellRange); procedure ReadColumns(ATableNode: TDOMNode); procedure ReadColumnStyle(AStyleNode: TDOMNode); procedure ReadConditionalFormats(ANode: TDOMNode; AWorksheet: TsBasicWorksheet); @@ -425,6 +426,19 @@ const 'number' // vkValue ); + CF_ICON_SET: array[TsCFIconSet] of string = ( + '3Arrows', '3ArrowsGray', '3Flags', // is3Arrows, is3ArrowsGray, is3Flags + '3TrafficLights1', '3TrafficLights2', // is3TrafficLights1, is3TrafficLights2 + '3Signs', '3Symbols', '3Symbols2', // is3Signs, is3Symbols, is3Symbols2 + '3Smilies', '3Stars', '3Triangles', // is3Smilies, is3Stars, is3Triangles + '3ColorSmilies', // is3ColorSmilies, + '4Arrows', '4ArrowsGray', // is4Arrows, is4ArrowsGray + '4RedToBlack', '4Rating', // is4RedToBlack, is4Rating, + '4RedToBlack', // is4TrafficLights, // not in ODS + '5Arrows', '5ArrowsGray', // is5Arrows, is5ArrowsGray + '5Rating', '5Quarters', '5Boxes' // is5Rating, is5Quarters, is5Boxes + ); + function CFOperandToStr(v: variant; AWorksheet: TsWorksheet): String; var r,c: Cardinal; @@ -2205,6 +2219,7 @@ begin 'calcext:condition': ReadCFCellFormat(childNode, AWorksheet, range); 'calcext:color-scale': ReadCFColorScale(childNode, AWorksheet, range); 'calcext:data-bar': ReadCFDataBars(childNode, AWorksheet, range); + 'calcext:icon-set': ReadCFIconSet(childNode, AWorksheet, range); end; childNode := childNode.NextSibling; end; @@ -4072,6 +4087,74 @@ begin ); end; +procedure TsSpreadOpenDocReader.ReadCFIconSet(ANode: TDOMNode; + ASheet: TsBasicWorksheet; ARange: TsCellRange); +{ + + + + } +var + sheet: TsWorksheet; + nodeName: String; + s: String; + values: array of double = nil; + kinds: array of TsCFValueKind = nil; + iconSet, tmp: TsCFIconSet; + sIconSet: String; + found: Boolean; + n: Integer; +begin + if ANode = nil then + exit; + + sIconSet := GetAttrValue(ANode, 'calcext:icon-set-type'); + if sIconSet = '' then + exit; + + for tmp in TsCFIconSet do + if sIconSet = CF_ICON_SET[tmp] then begin + iconSet := tmp; + found := true; + break; + end; + + if (not found) then + exit; + + // Number of icons + n := GetCFIconCount(iconSet); + if (n < 3) or (n > 5) then // only 3, 4 or 5 icons allowed + exit; + + ANode := ANode.FirstChild; + while ANode <> nil do + begin + nodeName := ANode.NodeName; + if nodeName = 'calcext:formatting-entry' then + begin + s := GetAttrValue(ANode, 'calcext:value'); + SetLength(values, Length(values)+1); + if not TryStrToFloat(s, values[High(values)], FPointSeparatorSettings) then + values[High(values)] := 0; + + s := GetAttrValue(ANode, 'calcext:type'); + SetLength(kinds, Length(kinds)+1); + kinds[High(kinds)] := StrToValueKind(s); + end; + ANode := ANode.NextSibling; + end; + + sheet := TsWorksheet(ASheet); + // Ignore the first value because it is always 0 + case n of + 3: sheet.WriteIconSet(ARange, iconSet, kinds[1], values[1], kinds[2], values[2]); + 4: sheet.WriteIconSet(ARange, iconSet, kinds[1], values[1], kinds[2], values[2], kinds[3], values[3]); + 5: sheet.WriteIconSet(ARange, iconSet, kinds[1], values[1], kinds[2], values[2], kinds[3], values[3], kinds[4], values[4]); + end; +end; + + { Reads the cells in the given table. Loops through all rows, and then finds all cells of each row. } procedure TsSpreadOpenDocReader.ReadRowsAndCells(ATableNode: TDOMNode); @@ -6263,7 +6346,8 @@ var cf_cellRule: TsCFCellRule; cf_DataBarRule: TsCFDataBarRule; cf_ColorRangeRule: TsCFColorRangeRule; - i,j: Integer; + cf_IconSetRule: TsCFIconSetRule; + i, j, k: Integer; sheet: TsWorksheet; rangeStr: String; firstCellStr: string; @@ -6358,6 +6442,24 @@ begin CF_VALUE_KIND[cf_ColorRangeRule.EndValueKind], ColorToHTMLColorStr(cf_ColorRangeRule.EndColor) ])); + end else + if cf.Rules[j] is TsCFIconSetRule then + begin + cf_IconSetRule := TsCFIconSetRule(cf.Rules[j]); + AppendToStream(AStream, Format( + '', [ + CF_ICON_SET[cf_IconSetRule.IconSet] + ])); + AppendToStream(AStream, + ''); + for k := 0 to cf_IconSetRule.IconCount-2 do + AppendToStream(AStream, Format( + '', [ + cf_IconSetRule.Values[k], + CF_VALUE_KIND[cf_IconSetRule.ValueKinds[k]] + ])); + AppendToStream(AStream, + ''); end; end; diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 89b00b5ff..b7f6fa9d4 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -405,6 +405,24 @@ type function WriteDataBars(ARange: TsCellRange; ABarColor: TsColor; AStartKind: TsCFValueKind; AStartValue: Double; AEndKind: TsCFValueKind; AEndValue: Double): Integer; overload; + // icon sets + function WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; overload; + function WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; // 3 icons + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; overload; + function WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; // 4 icons + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; overload; + function WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; // 5 icons + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AValueKind4: TsCFValueKind; AValue4: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; overload; { Formulas } function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula; diff --git a/components/fpspreadsheet/source/common/fpspreadsheet_cf.inc b/components/fpspreadsheet/source/common/fpspreadsheet_cf.inc index cc644ff7d..bdb5526b7 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet_cf.inc +++ b/components/fpspreadsheet/source/common/fpspreadsheet_cf.inc @@ -143,6 +143,72 @@ begin end; +{@@ ---------------------------------------------------------------------------- + Writes the conditional format "icon set" +-------------------------------------------------------------------------------} + +function TsWorksheet.WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; +begin + Result := FWorkbook.FConditionalFormatList.AddIconSetRule(Self, ARange, + AIconSet, + AHideText, AReverse); + StoreCFIndexInCells(self, Result, ARange); +end; + +function TsWorksheet.WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; +begin + Result := FWorkbook.FConditionalFormatList.AddIconSetRule(Self, ARange, + AIconSet, + AValueKind1, AValue1, + AValueKind2, AValue2, + AHideText, AReverse + ); + if Result <> -1 then + StoreCFIndexInCells(self, Result, ARange); +end; + +function TsWorksheet.WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; +begin + Result := FWorkbook.FConditionalFormatList.AddIconSetRule(Self, ARange, + AIconSet, + AValueKind1, AValue1, + AValueKind2, AValue2, + AValueKind3, AValue3, + AHideText, AReverse + ); + if Result <> -1 then + StoreCFIndexInCells(self, Result, ARange); +end; + +function TsWorksheet.WriteIconSet(ARange: TsCellRange; AIconSet: TsCFIconSet; + AValueKind1: TsCFValueKind; AValue1: Double; + AValueKind2: TsCFValueKind; AValue2: Double; + AValueKind3: TsCFValueKind; AValue3: Double; + AValueKind4: TsCFValueKind; AValue4: Double; + AHideText: Boolean = false; AReverse: Boolean = false): Integer; +begin + Result := FWorkbook.FConditionalFormatList.AddIconSetRule(Self, ARange, + AIconSet, + AValueKind1, AValue1, + AValueKind2, AValue2, + AValueKind3, AValue3, + AValueKind4, AValue4, + AHideText, AReverse + ); + if Result <> -1 then + StoreCFIndexInCells(self, Result, ARange); +end; + + + {==============================================================================} { TsWorkbook code for conditional formats } {==============================================================================} diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index d1dc7ad6f..1cf995585 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -82,6 +82,8 @@ type ARange: TsCellRange); procedure ReadCFExpression(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer); + procedure ReadCFIconSet(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; + ARange: TsCellRange); procedure ReadCFMisc(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer); procedure ReadCFTop10(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; @@ -159,18 +161,14 @@ type function PrepareFormula(const AFormula: String): String; procedure ResetStreams; procedure WriteBorderList(AStream: TStream); + procedure WriteCFCellRule(AStream: TStream; ARule: TsCFCellRule; ARange: TsCellRange; APriority: Integer); + procedure WriteCFColorRangeRule(AStream: TStream; ARule: TsCFColorRangeRule; APriority: Integer); + procedure WriteCFDataBarRule(AStream: TStream; ARule: TsCFDatabarRule; APriority: Integer); + procedure WriteCFIconSetRule(AStream: TStream; ARule: TsCFIconSetRule; APriority: Integer); procedure WriteColBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteCols(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteComments(AWorksheet: TsBasicWorksheet); procedure WriteConditionalFormat(AStream: TStream; AFormat: TsConditionalFormat; var APriority: Integer); - procedure WriteConditionalFormatCellRule(AStream: TStream; ARule: TsCFCellRule; - ARange: TsCellRange; APriority: Integer); - procedure WriteConditionalFormatColorRangeRule(AStream: TStream; ARule: TsCFColorRangeRule; - APriority: Integer); - procedure WriteConditionalFormatDataBarRule(AStream: TStream; ARule: TsCFDatabarRule; - APriority: Integer); - procedure WriteConditionalFormatRule(AStream: TStream; ARule: TsCFRule; - const ARange: TsCellRange; var APriority: Integer); procedure WriteConditionalFormats(AStream: TStream; AWorksheet: TsBasicWorksheet); procedure WriteDefinedNames(AStream: TStream); procedure WriteDifferentialFormat(AStream: TStream; AFormat: PsCellFormat); @@ -275,7 +273,7 @@ procedure InitOOXMLLimitations(out ALimitations: TsSpreadsheetFormatLimitations) implementation uses - variants, strutils, math, lazutf8, LazFileUtils, uriparser, + variants, strutils, math, lazutf8, LazFileUtils, uriparser, typinfo, {%H-}fpsPatches, fpSpreadsheet, fpsCrypto, fpsExprParser, fpsStrings, fpsStreams, fpsClasses, fpsImages; @@ -446,6 +444,20 @@ const '' // cfcExpression ); + CF_ICON_SET: array[TsCFIconSet] of string = ( + '3Arrows', '3ArrowsGray','3Flags', // is3Arrows, is3ArrowsGray, is3Flags + '3TrafficLights2', // REPLACEMENT FOR is3TrafficLights1 which requires x14 + '3TrafficLights', // is3TrafficLights2 + '3Signs', '3Symbols', '3Symbols2', // is3Signs, is3Symbols, is3Symbols2 + '3Signs', '3Signs', '3Signs', '3Signs', // REPLACEMENT FOR is3Smilies, is3Stars, is3Triangles, is3ColorSmilies which need x14 + '4Arrows', '4ArrowsGray', // is4Arrows, is4ArrowsGray + '4RedToBlack', '4Rating', // is4RedToBlack, is4Rating + '4TrafficLights', // is4TrafficLights, + '5Arrows', '5ArrowsGray', // is5Arrows, is5ArrowsGray, + '5Rating', '5Quarters', // is5Rating, is5Quarters, + '5Quarters' // REPLACEMENT FOR is5Boxes which needs x14 + ); + function StrToFillStyle(s: String): TsFillStyle; var fs: TsFillStyle; @@ -1533,6 +1545,79 @@ begin end; end; +procedure TsSpreadOOXMLReader.ReadCFIconSet(ANode: TDOMNode; + AWorksheet: TsBasicWorksheet; ARange: TsCellRange); +var + sheet: TsWorksheet; + s: String; + sIconSet: String; + iconSet: TsCFIconSet; + found: Boolean; + tmpIconSet: TsCFIconSet; + nodeName: String; + iv: Integer; + vk: array of TsCFValueKind = nil; + v: array of Double = nil; + n: Integer; + x: Double; + res: Integer; +begin + ANode := ANode.FirstChild; + if (ANode <> nil) and (ANode.NodeName = 'iconSet') then + begin + sIconSet := GetAttrValue(ANode, 'iconSet'); + found := false; + for tmpIconSet in TsCFIconSet do + if 'is' + sIconSet = GetEnumName(typeInfo(TsCFIconSet), integer(tmpIconSet)) then + begin + iconSet := tmpIconSet; + found := true; + break; + end; + + if (not found) or (sIconSet = '') then + Exit; + + // Determine icon count from name of icon set + n := GetCFIconCount(iconSet); + if (n < 3) or (n > 5) then // only 3, 4 or 5 icons allowed + Exit; + + SetLength(v, n); + SetLength(vk, n); + iv := 0; + ANode := ANode.FirstChild; + while (ANode <> nil) do + begin + nodeName := ANode.NodeName; + if (nodeName = 'cfvo') and (iv <= High(vk)) then + begin + s := GetAttrValue(ANode, 'type'); + vk[iv] := StrToCFValueKind(s); + s := GetAttrValue(ANode, 'val'); + if TryStrToFloat(s, x, FPointSeparatorSettings) then + v[iv] := x + else + v[iv] := 0.0; + inc(iv); + if iv >= n then + break; + end; + ANode := ANode.NextSibling; + end; + + sheet := TsWorksheet(AWorksheet); + // Ignore the first value because it is always 0 + case n of + 3: res := sheet.WriteIconSet(ARange, iconSet, vk[1], v[1], vk[2], v[2]); + 4: res := sheet.WriteIconSet(ARange, iconSet, vk[1], v[1], vk[2], v[2], vk[3], v[3]); + 5: res := sheet.WriteIconSet(ARange, iconSet, vk[1], v[1], vk[2], v[2], vk[3], v[3], vk[4], v[4]); + end; + + ANode := ANode.NextSibling; + end; +end; + procedure TsSpreadOOXMLReader.ReadCFMisc(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer); var @@ -1887,6 +1972,8 @@ begin ReadCFColorRange(childNode, AWorksheet, range); 'dataBar': ReadCFDataBars(childNode, AWorksheet, range); + 'iconSet': + ReadCFIconSet(childNode, AWorksheet, range); end; end; childNode := childNode.NextSibling; @@ -3913,6 +4000,203 @@ begin ''); end; + +procedure TsSpreadOOXMLWriter.WriteCFCellRule(AStream: TStream; + ARule: TsCFCellRule; ARange: TsCellRange; APriority: Integer); +const + FORMULA: array[cfcBeginsWith..cfcNotContainsErrors] of String = ( + 'LEFT(%0:s,LEN("%1:s"))="%1:s"', // cfcBeginsWith + 'RIGHT(%0:s,Len("%1:s"))="%1:s"', // cfcEndsWidth + 'NOT(ISERROR(SEARCH("%1:s",%0:s)))', // cfcContainsText + 'ISERROR(SEARCH("%1:s",%0:s))', // cfcNotContainsText + 'ISERROR(%0:s)', // cfcContainsErrors + 'NOT(ISERROR(%0:s))' // cfcNotContainsErrors + ); +var + i: Integer; + dxfID: Integer; + typeStr, opStr, formula1Str, formula2Str, param1Str, param2Str, param3Str: String; + firstCellOfRange: String; + s: String; +begin + dxfID := -1; + for i := 0 to High(FDifferentialFormatIndexList) do + if FDifferentialFormatIndexList[i] = ARule.FormatIndex then + begin + dxfID := i; + break; + end; + + typeStr := CF_TYPE_NAMES[ARule.Condition]; + if CF_OPERATOR_NAMES[ARule.Condition] = '' then + opStr := '' + else + opStr := ' operator="' + CF_OPERATOR_NAMES[ARule.Condition] + '"'; + formula1Str := ''; + formula2Str := ''; + param1Str := ''; + param2Str := ''; + param3Str := ''; + case ARule.Condition of + cfcEqual..cfcNotBetween: + begin + s := CFOperandToStr(ARule.Operand1); + formula1Str := Format('%s', [s]); + if (ARule.Condition in [cfcBetween, cfcNotBetween]) then + begin + s := CFOperandToStr(ARule.Operand2); + formula2Str := Format('%s', [s]); + end; + end; + cfcAboveAverage..cfcBelowEqualAverage: + begin + if (ARule.Condition in [cfcBelowAverage, cfcBelowEqualAverage]) then + param1Str := ' aboveAverage="0"'; + if (ARule.Condition in [cfcAboveEqualAverage, cfcBelowEqualAverage]) then + param2Str := ' equalAverage="1"'; + if VarIsNumeric(ARule.Operand1) or (ARule.Operand1 = 0) then + param3Str := Format(' stdDev="%g"', [double(ARule.Operand1)]); + end; + cfcTop, cfcBottom, cfcTopPercent, cfcBottomPercent: + begin + // // = bottom 30 percent + if ARule.Condition in [cfcBottom, cfcBottomPercent] then + param1Str := ' bottom="1"'; + if ARule.Condition in [cfcTopPercent, cfcBottomPercent] then + param2Str := ' percent="1"'; + param3Str := ' rank="' + VarToStr(ARule.Operand1) + '"'; + end; + cfcDuplicate, cfcUnique: + ; + cfcBeginsWith..cfcNotContainsErrors: + begin + firstCellOfRange := GetCellString(ARange.Row1, ARange.Col1); + formula1Str := + '' + + Format(FORMULA[ARule.Condition], [firstcellOfRange, ARule.Operand1]) + + ''; + param1Str := ' text="' + VarToStr(ARule.Operand1) + '"'; + end; + cfcExpression: + begin + s := ARule.Operand1; + if (s <> '') and (s[1] = '=') then Delete(s, 1, 1); + formula1Str := '' + s + ''; + end; + else + FWorkbook.AddErrorMsg('ConditionalFormat operator not supported.'); + end; + + if formula1Str = '' then + s := Format( + '', [ + typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str + ]) + else + s := Format( + '' + + '%s%s' + + '', [ + typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str, + formula1Str, formula2Str + ]); + AppendToStream(AStream, s); +end; + + +procedure TsSpreadOOXMLWriter.WriteCFColorRangeRule(AStream: TStream; + ARule: TsCFColorRangeRule; APriority: Integer); +{ example: + + + + + + + + + + } +begin + AppendToStream(AStream, + '' + + ''); + AppendToStream(AStream, + CF_ValueNode(ARule.StartValueKind, ARule.StartValue), + IfThen(ARule.ThreeColors, CF_ValueNode(ARule.CenterValueKind, ARule.CenterValue), ''), + CF_ValueNode(ARule.EndValueKind, ARule.EndValue) + ); + AppendToStream(AStream, + CF_ColorNode(ARule.StartColor), + IfThen(ARule.ThreeColors, CF_ColorNode(ARule.CenterColor), ''), + CF_ColorNode(ARule.EndColor) + ); + AppendToStream(AStream, + '' + + ''); +end; + + +procedure TsSpreadOOXMLWriter.WriteCFDatabarRule(AStream: TStream; + ARule: TsCFDataBarRule; APriority: Integer); +{ example from test file: + + + + + + + + + {A620EE03-2FEC-4D54-872C-66BDB99CB07E} + + + } +begin + AppendToStream(AStream, + '' + + ''); + + AppendToStream(AStream, + CF_ValueNode(ARule.StartValueKind, ARule.StartValue), + CF_ValueNode(ARule.EndValueKind, ARule.EndValue), + CF_ColorNode(ARule.Color) ); + + AppendToStream(AStream, + '' + + ''); +end; + + +procedure TsSpreadOOXMLWriter.WriteCFIconSetRule(AStream: TStream; + ARule: TsCFIconSetRule; APriority: Integer); +{ + + + + + + } +var + i: Integer; +begin + AppendToStream(AStream, Format( + '' + + '' + + '', [ + APriority, CF_ICON_SET[Arule.IconSet] + ])); + + for i := 0 to ARule.IconCount-2 do + AppendToStream(AStream, + CF_ValueNode(ARule.ValueKinds[i], ARule.Values[i]) + ); + AppendToStream(AStream, + '' + + ''); +end; + + procedure TsSpreadOOXMLWriter.WriteColBreaks(AStream: TStream; AWorksheet: TsBasicWorksheet); var @@ -4045,192 +4329,25 @@ begin for i := 0 to AFormat.RulesCount-1 do begin rule := AFormat.Rules[i]; - WriteConditionalFormatRule(AStream, rule, AFormat.CellRange, APriority); + if rule is TsCFCellRule then + WriteCFCellRule(AStream, TsCFCellRule(rule), AFormat.CellRange, APriority) + else + if rule is TsCFColorRangeRule then + WriteCFColorRangeRule(AStream, TsCFColorRangeRule(rule), APriority) + else + if rule is TsCFDataBarRule then + WriteCFDataBarRule(AStream, TsCFDataBarRule(rule), APriority) + else + if rule is TsCFIconSetRule then + WriteCFIconSetRule(AStream, TsCFIconSetRule(rule), APriority) + else + exit; + dec(APriority); end; AppendToStream(AStream, ''); end; -procedure TsSpreadOOXMLWriter.WriteConditionalFormatCellRule(AStream: TStream; - ARule: TsCFCellRule; ARange: TsCellRange; APriority: Integer); -const - FORMULA: array[cfcBeginsWith..cfcNotContainsErrors] of String = ( - 'LEFT(%0:s,LEN("%1:s"))="%1:s"', // cfcBeginsWith - 'RIGHT(%0:s,Len("%1:s"))="%1:s"', // cfcEndsWidth - 'NOT(ISERROR(SEARCH("%1:s",%0:s)))', // cfcContainsText - 'ISERROR(SEARCH("%1:s",%0:s))', // cfcNotContainsText - 'ISERROR(%0:s)', // cfcContainsErrors - 'NOT(ISERROR(%0:s))' // cfcNotContainsErrors - ); -var - i: Integer; - dxfID: Integer; - typeStr, opStr, formula1Str, formula2Str, param1Str, param2Str, param3Str: String; - firstCellOfRange: String; - s: String; -begin - dxfID := -1; - for i := 0 to High(FDifferentialFormatIndexList) do - if FDifferentialFormatIndexList[i] = ARule.FormatIndex then - begin - dxfID := i; - break; - end; - - typeStr := CF_TYPE_NAMES[ARule.Condition]; - if CF_OPERATOR_NAMES[ARule.Condition] = '' then - opStr := '' - else - opStr := ' operator="' + CF_OPERATOR_NAMES[ARule.Condition] + '"'; - formula1Str := ''; - formula2Str := ''; - param1Str := ''; - param2Str := ''; - param3Str := ''; - case ARule.Condition of - cfcEqual..cfcNotBetween: - begin - s := CFOperandToStr(ARule.Operand1); - formula1Str := Format('%s', [s]); - if (ARule.Condition in [cfcBetween, cfcNotBetween]) then - begin - s := CFOperandToStr(ARule.Operand2); - formula2Str := Format('%s', [s]); - end; - end; - cfcAboveAverage..cfcBelowEqualAverage: - begin - if (ARule.Condition in [cfcBelowAverage, cfcBelowEqualAverage]) then - param1Str := ' aboveAverage="0"'; - if (ARule.Condition in [cfcAboveEqualAverage, cfcBelowEqualAverage]) then - param2Str := ' equalAverage="1"'; - if VarIsNumeric(ARule.Operand1) or (ARule.Operand1 = 0) then - param3Str := Format(' stdDev="%g"', [double(ARule.Operand1)]); - end; - cfcTop, cfcBottom, cfcTopPercent, cfcBottomPercent: - begin - // // = bottom 30 percent - if ARule.Condition in [cfcBottom, cfcBottomPercent] then - param1Str := ' bottom="1"'; - if ARule.Condition in [cfcTopPercent, cfcBottomPercent] then - param2Str := ' percent="1"'; - param3Str := ' rank="' + VarToStr(ARule.Operand1) + '"'; - end; - cfcDuplicate, cfcUnique: - ; - cfcBeginsWith..cfcNotContainsErrors: - begin - firstCellOfRange := GetCellString(ARange.Row1, ARange.Col1); - formula1Str := - '' + - Format(FORMULA[ARule.Condition], [firstcellOfRange, ARule.Operand1]) + - ''; - param1Str := ' text="' + VarToStr(ARule.Operand1) + '"'; - end; - cfcExpression: - begin - s := ARule.Operand1; - if (s <> '') and (s[1] = '=') then Delete(s, 1, 1); - formula1Str := '' + s + ''; - end; - else - FWorkbook.AddErrorMsg('ConditionalFormat operator not supported.'); - end; - - if formula1Str = '' then - s := Format( - '', [ - typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str - ]) - else - s := Format( - '' + - '%s%s' + - '', [ - typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str, - formula1Str, formula2Str - ]); - AppendToStream(AStream, s); -end; - -procedure TsSpreadOOXMLWriter.WriteConditionalFormatColorRangeRule(AStream: TStream; - ARule: TsCFColorRangeRule; APriority: Integer); -{ example: - - - - - - - - - - } -begin - AppendToStream(AStream, - '' + - ''); - AppendToStream(AStream, - CF_ValueNode(ARule.StartValueKind, ARule.StartValue), - IfThen(ARule.ThreeColors, CF_ValueNode(ARule.CenterValueKind, ARule.CenterValue), ''), - CF_ValueNode(ARule.EndValueKind, ARule.EndValue) - ); - AppendToStream(AStream, - CF_ColorNode(ARule.StartColor), - IfThen(ARule.ThreeColors, CF_ColorNode(ARule.CenterColor), ''), - CF_ColorNode(ARule.EndColor) - ); - AppendToStream(AStream, - '' + - ''); -end; - -procedure TsSpreadOOXMLWriter.WriteConditionalFormatDatabarRule(AStream: TStream; - ARule: TsCFDataBarRule; APriority: Integer); -{ example from test file: - - - - - - - - - {A620EE03-2FEC-4D54-872C-66BDB99CB07E} - - - } -begin - AppendToStream(AStream, - '' + - ''); - - AppendToStream(AStream, - CF_ValueNode(ARule.StartValueKind, ARule.StartValue), - CF_ValueNode(ARule.EndValueKind, ARule.EndValue), - CF_ColorNode(ARule.Color) ); - - AppendToStream(AStream, - '' + - ''); -end; - -procedure TsSpreadOOXMLWriter.WriteConditionalFormatRule(AStream: TStream; - ARule: TsCFRule; const ARange: TsCellRange; var APriority: Integer); -begin - if ARule is TsCFCellRule then - WriteConditionalFormatCellRule(AStream, TsCFCellRule(ARule), ARange, APriority) - else - if ARule is TsCFColorRangeRule then - WriteConditionalFormatColorRangeRule(AStream, TsCFColorRangeRule(ARule), APriority) - else - if ARule is TsCFDataBarRule then - WriteConditionalFormatDataBarRule(AStream, TsCFDataBarRule(ARule), APriority) - else - exit; - dec(APriority); -end; - procedure TsSpreadOOXMLWriter.WriteConditionalFormats(AStream: TStream; AWorksheet: TsBasicWorksheet); var diff --git a/components/fpspreadsheet/tests/conditionalformattests.pas b/components/fpspreadsheet/tests/conditionalformattests.pas index 7a7532cae..b673212b4 100644 --- a/components/fpspreadsheet/tests/conditionalformattests.pas +++ b/components/fpspreadsheet/tests/conditionalformattests.pas @@ -34,12 +34,18 @@ type procedure TestWriteRead_CF_CellFmt(AFileFormat: TsSpreadsheetFormat; ACondition: TsCFCondition; ACellFormat: TsCellFormat); + // Test color range format procedure TestWriteRead_CF_ColorRange(AFileFormat: TsSpreadsheetFormat; ThreeColors: Boolean; FullSyntax: Boolean); + // Test data bars format procedure TestWriteRead_CF_DataBars(AFileFormat: TsSpreadsheetFormat; FullSyntax: Boolean); + // Test icon set format + procedure TestWriteRead_CF_IconSet(AFileFormat: TsSpreadsheetFormat; + AIconSet: TsCFIconSet; FullSyntax: Boolean); + published { Excel XLSX } procedure TestWriteRead_CF_CellFmt_XLSX_Equal_Const; @@ -81,6 +87,10 @@ type procedure TestWriteRead_CF_Databars_XLSX_Full; procedure TestWriteRead_CF_Databars_XLSX_Simple; + procedure TestWriteRead_CF_Iconset_XLSX_Full_5Quarters; + procedure TestWriteRead_CF_IconSet_XLSX_Simple_3Arrows; + procedure TestWriteRead_CF_IconSet_XLSX_Simple_5Rating; + { Excel XML } procedure TestWriteRead_CF_CellFmt_XML_Equal_Const; procedure TestWriteRead_CF_CellFmt_XML_NotEqual_Const; @@ -151,6 +161,9 @@ type procedure TestWriteRead_CF_Databars_ODS_Full; procedure TestWriteRead_CF_Databars_ODS_Simple; + procedure TestWriteRead_CF_Iconset_ODS_Full_5Quarters; + procedure TestWriteRead_CF_IconSet_ODS_Simple_3Arrows; + procedure TestWriteRead_CF_IconSet_ODS_Simple_5Rating; end; implementation @@ -1541,6 +1554,188 @@ begin end; + +{------------------------------------------------------------------------------- + IconSet tests +-------------------------------------------------------------------------------} +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_IconSet( + AFileFormat: TsSpreadsheetFormat; AIconSet: TsCFIconSet; FullSyntax: Boolean); +const + SHEET_NAME = 'CF'; + SOLL_VALUE_KIND_1 = vkValue; + SOLL_VALUE_KIND_2 = vkPercent; + SOLL_VALUE_KIND_3 = vkPercentile; + SOLL_VALUE_KIND_4 = vkPercent; + SOLL_VALUE_1 = 15; + SOLL_VALUE_2 = 42; + SOLL_VALUE_3 = 62; + SOLL_VALUE_4 = 85; + +var + worksheet: TsWorksheet; + workbook: TsWorkbook; + row, col: Cardinal; + tempFile: string; + cf: TsConditionalFormat; + rule: TsCFIconSetRule; + sollRange: TsCellRange; + actRange: TsCellRange; + actIconSet: TsCFIconSet; + n: Integer; +begin + // Write out all test values + workbook := TsWorkbook.Create; + try + workbook.Options := [boAutoCalc]; + workSheet:= workBook.AddWorksheet(SHEET_NAME); + + // Add test cells (numeric) + row := 0; + for Col := 0 to 9 do + worksheet.WriteNumber(row, col, col*10.0); + + // Write conditional formats + sollRange := Range(0, 0, 0, 9); + if FullSyntax then + begin + n := GetCFIconCount(AIconSet); + case n of + 3: worksheet.WriteIconSet(sollRange, AIconSet, SOLL_VALUE_KIND_1, SOLL_VALUE_1, SOLL_VALUE_KIND_2, SOLL_VALUE_2); + 4: worksheet.WriteIconSet(sollRange, AIconSet, SOLL_VALUE_KIND_1, SOLL_VALUE_1, SOLL_VALUE_KIND_2, SOLL_VALUE_2, SOLL_VALUE_KIND_3, SOLL_VALUE_3); + 5: worksheet.WriteIconSet(sollRange, AIconSet, SOLL_VALUE_KIND_1, SOLL_VALUE_1, SOLL_VALUE_KIND_2, SOLL_VALUE_2, SOLL_VALUE_KIND_3, SOLL_VALUE_3, SOLL_VALUE_KIND_4, SOLL_VALUE_4); + end; + end else + worksheet.WriteIconSet(sollRange, AIconSet); + + // Save to file + tempFile := NewTempFile; + workBook.WriteToFile(tempFile, AFileFormat, true); + finally + workbook.Free; + end; + + // Open the spreadsheet + workbook := TsWorkbook.Create; + try + workbook.ReadFromFile(TempFile, AFileFormat); + worksheet := GetWorksheetByName(workBook, SHEET_NAME); + + if worksheet=nil then + fail('Error in test code. Failed to get named worksheet'); + + // Check count of conditional formats + CheckEquals(1, workbook.GetNumConditionalFormats, 'ConditionalFormat count mismatch.'); + + // Read conditional format + cf := Workbook.GetConditionalFormat(0); + + //Check range + actRange := cf.CellRange; + CheckEquals(sollRange.Row1, actRange.Row1, 'Conditional format range mismatch (Row1)'); + checkEquals(sollRange.Col1, actRange.Col1, 'Conditional format range mismatch (Col1)'); + CheckEquals(sollRange.Row2, actRange.Row2, 'Conditional format range mismatch (Row2)'); + checkEquals(sollRange.Col2, actRange.Col2, 'Conditional format range mismatch (Col2)'); + + // Check rules count + CheckEquals(1, cf.RulesCount, 'Conditional format rules count mismatch'); + + // Check rules class + CheckEquals(TsCFIconSetRule, cf.Rules[0].ClassType, 'Conditional format rule class mismatch'); + + // Now know that the rule is a TsCFIconsetRule + rule := TsCFIconSetRule(cf.Rules[0]); + + // Check icon set + actIconSet := rule.IconSet; + CheckEquals( + GetEnumName(TypeInfo(TsCFIconSet), Integer(AIconSet)), + GetEnumName(TypeInfo(TsCFIconSet), Integer(actIconSet)), + 'IconSet format: icon set mismatch'); + + // Parameters + if FullSyntax then + begin + CheckEquals( + GetEnumName(TypeInfo(TsCFValueKind), integer(SOLL_VALUE_KIND_1)), + GetEnumName(TypeInfo(TsCFValueKind), integer(rule.ValueKinds[0])), + 'IconSet format: value kind 0 mismatch.' + ); + if not (SOLL_VALUE_KIND_1 in [vkMin, vkMax]) then + CheckEquals(SOLL_VALUE_1, rule.Values[0], 'IconSet format: value 0 mismatch'); + + CheckEquals( + GetEnumName(TypeInfo(TsCFValueKind), integer(SOLL_VALUE_KIND_2)), + GetEnumName(TypeInfo(TsCFValueKind), integer(rule.ValueKinds[1])), + 'IconSet format: value kind 1 mismatch.' + ); + if not (SOLL_VALUE_KIND_2 in [vkMin, vkMax]) then + CheckEquals(SOLL_VALUE_2, rule.Values[1], 'IconSet format: value 1 mismatch'); + + if n > 2 then + begin + CheckEquals( + GetEnumName(TypeInfo(TsCFValueKind), integer(SOLL_VALUE_KIND_3)), + GetEnumName(TypeInfo(TsCFValueKind), integer(rule.ValueKinds[2])), + 'IconSet format: value kind 2 mismatch.' + ); + if not (SOLL_VALUE_KIND_3 in [vkMin, vkMax]) then + CheckEquals(SOLL_VALUE_3, rule.Values[2], 'IconSet format: value 2 mismatch'); + end; + + if n = 3 then + begin + CheckEquals( + GetEnumName(TypeInfo(TsCFValueKind), integer(SOLL_VALUE_KIND_4)), + GetEnumName(TypeInfo(TsCFValueKind), integer(rule.ValueKinds[3])), + 'IconSet format: value kind 3 mismatch.' + ); + if not (SOLL_VALUE_KIND_4 in [vkMin, vkMax]) then + CheckEquals(SOLL_VALUE_4, rule.Values[3], 'IconSet format: value 3 mismatch'); + end; + end; + + finally + workbook.Free; + DeleteFile(tempFile); + end; +end; + + +{ Excel XLSX } + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_Iconset_XLSX_Full_5Quarters; +begin + TestWriteRead_CF_IconSet(sfOOXML, is5Quarters, true); +end; + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_IconSet_XLSX_Simple_3Arrows; +begin + TestWriteRead_CF_IconSet(sfOOXML, is3Arrows, false); +end; + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_IconSet_XLSX_Simple_5Rating; +begin + TestWriteRead_CF_IconSet(sfOOXML, is5Rating, false); +end; + +{ OpenDocument } + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_Iconset_ODS_Full_5Quarters; +begin + TestWriteRead_CF_IconSet(sfOpenDocument, is5Quarters, true); +end; + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_IconSet_ODS_Simple_3Arrows; +begin + TestWriteRead_CF_IconSet(sfOpenDocument, is3Arrows, false); +end; + +procedure TSpreadWriteReadCFTests.TestWriteRead_CF_IconSet_ODS_Simple_5Rating; +begin + TestWriteRead_CF_IconSet(sfOpenDocument, is5Rating, false); +end; + + initialization RegisterTest(TSpreadWriteReadCFTests); diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index c1922ef74..7dc947e60 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -1,13 +1,11 @@ - + - - - + <ResourceType Value="res"/> </General>