You've already forked lazarus-ccr
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
This commit is contained in:
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
{ <calcext:icon-set calcext:icon-set-type="3Stars">
|
||||
<calcext:formatting-entry calcext:value="0" calcext:type="percent" />
|
||||
<calcext:formatting-entry calcext:value="33" calcext:type="percent" />
|
||||
<calcext:formatting-entry calcext:value="66" calcext:type="percent" />
|
||||
</calcext:icon-set> }
|
||||
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(
|
||||
'<calcext:icon-set calcext:icon-set-type="%s">', [
|
||||
CF_ICON_SET[cf_IconSetRule.IconSet]
|
||||
]));
|
||||
AppendToStream(AStream,
|
||||
'<calcext:formatting-entry calcext:value="0" calcext:type="percent" />');
|
||||
for k := 0 to cf_IconSetRule.IconCount-2 do
|
||||
AppendToStream(AStream, Format(
|
||||
'<calcext:formatting-entry calcext:value="%g" calcext:type="%s" />', [
|
||||
cf_IconSetRule.Values[k],
|
||||
CF_VALUE_KIND[cf_IconSetRule.ValueKinds[k]]
|
||||
]));
|
||||
AppendToStream(AStream,
|
||||
'</calcext:icon-set>');
|
||||
end;
|
||||
end;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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 }
|
||||
{==============================================================================}
|
||||
|
@@ -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
|
||||
'</borders>');
|
||||
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('<formula>%s</formula>', [s]);
|
||||
if (ARule.Condition in [cfcBetween, cfcNotBetween]) then
|
||||
begin
|
||||
s := CFOperandToStr(ARule.Operand2);
|
||||
formula2Str := Format('<formula>%s</formula>', [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
|
||||
// <cfRule type="top10" dxfId="0" priority="1" percent="1" bottom="1" rank="30" /> // = 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 :=
|
||||
'<formula>' +
|
||||
Format(FORMULA[ARule.Condition], [firstcellOfRange, ARule.Operand1]) +
|
||||
'</formula>';
|
||||
param1Str := ' text="' + VarToStr(ARule.Operand1) + '"';
|
||||
end;
|
||||
cfcExpression:
|
||||
begin
|
||||
s := ARule.Operand1;
|
||||
if (s <> '') and (s[1] = '=') then Delete(s, 1, 1);
|
||||
formula1Str := '<formula>' + s + '</formula>';
|
||||
end;
|
||||
else
|
||||
FWorkbook.AddErrorMsg('ConditionalFormat operator not supported.');
|
||||
end;
|
||||
|
||||
if formula1Str = '' then
|
||||
s := Format(
|
||||
'<cfRule type="%s" dxfId="%d" priority="%d"%s%s%s%s />', [
|
||||
typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str
|
||||
])
|
||||
else
|
||||
s := Format(
|
||||
'<cfRule type="%s" dxfId="%d" priority="%d"%s%s%s%s>' +
|
||||
'%s%s' +
|
||||
'</cfRule>', [
|
||||
typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str,
|
||||
formula1Str, formula2Str
|
||||
]);
|
||||
AppendToStream(AStream, s);
|
||||
end;
|
||||
|
||||
|
||||
procedure TsSpreadOOXMLWriter.WriteCFColorRangeRule(AStream: TStream;
|
||||
ARule: TsCFColorRangeRule; APriority: Integer);
|
||||
{ example:
|
||||
<cfRule type="colorScale" priority="3">
|
||||
<colorScale>
|
||||
<cfvo type="min" />
|
||||
<cfvo type="percentile" val="50" />
|
||||
<cfvo type="max" />
|
||||
<color rgb="FFF8696B" />
|
||||
<color rgb="FFFFEB84" />
|
||||
<color rgb="FF63BE7B" />
|
||||
</colorScale>
|
||||
</cfRule> }
|
||||
begin
|
||||
AppendToStream(AStream,
|
||||
'<cfRule type="colorScale" priority="' + IntToStr(APriority) + '">' +
|
||||
'<colorScale>');
|
||||
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,
|
||||
'</colorScale>' +
|
||||
'</cfRule>');
|
||||
end;
|
||||
|
||||
|
||||
procedure TsSpreadOOXMLWriter.WriteCFDatabarRule(AStream: TStream;
|
||||
ARule: TsCFDataBarRule; APriority: Integer);
|
||||
{ example from test file:
|
||||
<cfRule type="dataBar" priority="1">
|
||||
<dataBar>
|
||||
<cfvo type="min" />
|
||||
<cfvo type="max" />
|
||||
<color rgb="FF638EC6" />
|
||||
</dataBar>
|
||||
<extLst>
|
||||
<ext uri="{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
|
||||
<x14:id>{A620EE03-2FEC-4D54-872C-66BDB99CB07E}</x14:id>
|
||||
</ext>
|
||||
</extLst>
|
||||
</cfRule> }
|
||||
begin
|
||||
AppendToStream(AStream,
|
||||
'<cfRule type="dataBar" priority="' + IntToStr(APriority) + '">' +
|
||||
'<dataBar>');
|
||||
|
||||
AppendToStream(AStream,
|
||||
CF_ValueNode(ARule.StartValueKind, ARule.StartValue),
|
||||
CF_ValueNode(ARule.EndValueKind, ARule.EndValue),
|
||||
CF_ColorNode(ARule.Color) );
|
||||
|
||||
AppendToStream(AStream,
|
||||
'</dataBar>' +
|
||||
'</cfRule>');
|
||||
end;
|
||||
|
||||
|
||||
procedure TsSpreadOOXMLWriter.WriteCFIconSetRule(AStream: TStream;
|
||||
ARule: TsCFIconSetRule; APriority: Integer);
|
||||
{ <cfRule type="iconSet" priority="13">
|
||||
<iconSet iconSet="3Symbols2">
|
||||
<cfvo type="percent" val="0" />
|
||||
<cfvo type="percent" val="33" />
|
||||
<cfvo type="percent" val="67" />
|
||||
</iconSet>
|
||||
</cfRule> }
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
AppendToStream(AStream, Format(
|
||||
'<cfRule type="iconSet" priority="%d">' +
|
||||
'<iconSet iconSet="%s">' +
|
||||
'<cfvo type="percent" val="0" />', [
|
||||
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,
|
||||
'</iconSet>' +
|
||||
'</cfRule>');
|
||||
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,
|
||||
'</conditionalFormatting>');
|
||||
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('<formula>%s</formula>', [s]);
|
||||
if (ARule.Condition in [cfcBetween, cfcNotBetween]) then
|
||||
begin
|
||||
s := CFOperandToStr(ARule.Operand2);
|
||||
formula2Str := Format('<formula>%s</formula>', [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
|
||||
// <cfRule type="top10" dxfId="0" priority="1" percent="1" bottom="1" rank="30" /> // = 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 :=
|
||||
'<formula>' +
|
||||
Format(FORMULA[ARule.Condition], [firstcellOfRange, ARule.Operand1]) +
|
||||
'</formula>';
|
||||
param1Str := ' text="' + VarToStr(ARule.Operand1) + '"';
|
||||
end;
|
||||
cfcExpression:
|
||||
begin
|
||||
s := ARule.Operand1;
|
||||
if (s <> '') and (s[1] = '=') then Delete(s, 1, 1);
|
||||
formula1Str := '<formula>' + s + '</formula>';
|
||||
end;
|
||||
else
|
||||
FWorkbook.AddErrorMsg('ConditionalFormat operator not supported.');
|
||||
end;
|
||||
|
||||
if formula1Str = '' then
|
||||
s := Format(
|
||||
'<cfRule type="%s" dxfId="%d" priority="%d"%s%s%s%s />', [
|
||||
typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str
|
||||
])
|
||||
else
|
||||
s := Format(
|
||||
'<cfRule type="%s" dxfId="%d" priority="%d"%s%s%s%s>' +
|
||||
'%s%s' +
|
||||
'</cfRule>', [
|
||||
typeStr, dxfId, APriority, opStr, param1Str, param2Str, param3Str,
|
||||
formula1Str, formula2Str
|
||||
]);
|
||||
AppendToStream(AStream, s);
|
||||
end;
|
||||
|
||||
procedure TsSpreadOOXMLWriter.WriteConditionalFormatColorRangeRule(AStream: TStream;
|
||||
ARule: TsCFColorRangeRule; APriority: Integer);
|
||||
{ example:
|
||||
<cfRule type="colorScale" priority="3">
|
||||
<colorScale>
|
||||
<cfvo type="min" />
|
||||
<cfvo type="percentile" val="50" />
|
||||
<cfvo type="max" />
|
||||
<color rgb="FFF8696B" />
|
||||
<color rgb="FFFFEB84" />
|
||||
<color rgb="FF63BE7B" />
|
||||
</colorScale>
|
||||
</cfRule> }
|
||||
begin
|
||||
AppendToStream(AStream,
|
||||
'<cfRule type="colorScale" priority="' + IntToStr(APriority) + '">' +
|
||||
'<colorScale>');
|
||||
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,
|
||||
'</colorScale>' +
|
||||
'</cfRule>');
|
||||
end;
|
||||
|
||||
procedure TsSpreadOOXMLWriter.WriteConditionalFormatDatabarRule(AStream: TStream;
|
||||
ARule: TsCFDataBarRule; APriority: Integer);
|
||||
{ example from test file:
|
||||
<cfRule type="dataBar" priority="1">
|
||||
<dataBar>
|
||||
<cfvo type="min" />
|
||||
<cfvo type="max" />
|
||||
<color rgb="FF638EC6" />
|
||||
</dataBar>
|
||||
<extLst>
|
||||
<ext uri="{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
|
||||
<x14:id>{A620EE03-2FEC-4D54-872C-66BDB99CB07E}</x14:id>
|
||||
</ext>
|
||||
</extLst>
|
||||
</cfRule> }
|
||||
begin
|
||||
AppendToStream(AStream,
|
||||
'<cfRule type="dataBar" priority="' + IntToStr(APriority) + '">' +
|
||||
'<dataBar>');
|
||||
|
||||
AppendToStream(AStream,
|
||||
CF_ValueNode(ARule.StartValueKind, ARule.StartValue),
|
||||
CF_ValueNode(ARule.EndValueKind, ARule.EndValue),
|
||||
CF_ColorNode(ARule.Color) );
|
||||
|
||||
AppendToStream(AStream,
|
||||
'</dataBar>' +
|
||||
'</cfRule>');
|
||||
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
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -1,13 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<Version Value="11"/>
|
||||
<PathDelim Value="\"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<CompatibilityMode Value="True"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<MainUnit Value="0"/>
|
||||
<Title Value="spreadtestgui"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
|
Reference in New Issue
Block a user