fpspreadsheet: Complete of conditional formatting from xlsx. Untested.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7527 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz 2020-07-07 21:48:03 +00:00
parent 9bc1afa59b
commit 57fc4d5095
3 changed files with 335 additions and 64 deletions
components/fpspreadsheet/source/common

View File

@ -35,7 +35,7 @@ type
procedure Assign(ASource: TsCFRule); override;
end;
TsCFValueKind = (vkMin, vkMax, vkPercent, vkPercentile, vkValue);
TsCFValueKind = (vkNone, vkMin, vkMax, vkPercent, vkPercentile, vkValue);
{ Color range }
TsCFColorRangeRule = class(TsCFRule)
@ -62,7 +62,7 @@ type
EndValueKind: TsCFValueKind;
StartValue: Double;
EndValue: Double;
BarColor: TsColor;
Color: TsColor;
constructor Create;
procedure Assign(ASource: TsCFRule); override;
end;
@ -134,6 +134,7 @@ type
implementation
uses
Math,
fpSpreadsheet;
procedure TsCFCellRule.Assign(ASource: TsCFRule);
@ -153,7 +154,7 @@ begin
inherited;
StartValueKind := vkMin;
EndValueKind := vkMax;
BarColor := scBlue;
Color := scBlue;
end;
procedure TsCFDataBarRule.Assign(ASource: TsCFRule);
@ -359,6 +360,7 @@ var
begin
rule := TsCFColorRangeRule.Create;
rule.StartColor := AStartColor;
rule.CenterColor := scNotDefined;
rule.EndColor := AEndColor;
rule.ThreeColors := false;
Result := AddRule(ASheet, ARange, rule);
@ -373,6 +375,7 @@ var
begin
rule := TsCFColorRangeRule.Create;
rule.SetupStart(AStartColor, AStartKind, AStartValue);
rule.SetupCenter(scNotDefined, vkNone, NaN);
rule.SetupEnd(AEndColor, AEndKind, AEndValue);
rule.ThreeColors := false;
Result := AddRule(ASheet, ARange, rule);
@ -400,7 +403,7 @@ var
rule: TsCFDataBarRule;
begin
rule := TsCFDataBarRule.Create;
rule.BarColor:= ABarColor;
rule.Color := ABarColor;
Result := AddRule(ASheet, ARange, rule);
end;
@ -411,7 +414,7 @@ var
rule: TsCFDataBarRule;
begin
rule := TsCFDataBarRule.Create;
rule.BarColor:= ABarColor;
rule.Color:= ABarColor;
rule.StartValueKind := AStartKind;
rule.StartValue := AStartValue;
rule.EndValueKind := AEndKind;

View File

@ -411,6 +411,7 @@ const
);
CF_VALUE_KIND: array[TsCFValueKind] of string = (
'', // vkNone
'minimum', // vkMin
'maximum', // vkMax
'percent', // vkPercent
@ -5954,7 +5955,7 @@ begin
'<calcext:formatting-entry calcext:value="%g" calcext:type="%s" />' +
'<calcext:formatting-entry calcext:value="%g" calcext:type="%s" />' +
'</calcext:data-bar>', [
ColorToHTMLColorStr(cf_DatabarRule.BarColor), ColorToHTMLColorStr(cf_DatabarRule.BarColor),
ColorToHTMLColorStr(cf_DatabarRule.Color), ColorToHTMLColorStr(cf_DatabarRule.Color),
cf_DatabarRule.StartValue, CF_VALUE_KIND[cf_DatabarRule.StartValueKind],
cf_DatabarRule.EndValue, CF_VALUE_KIND[cf_DatabarRule.EndValueKind]
]));

View File

@ -73,7 +73,18 @@ type
procedure ReadCell(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARowIndex: Cardinal; var AColIndex: Cardinal);
procedure ReadCellXfs(ANode: TDOMNode);
procedure ReadCFRule(ANode: TDOMNode; AWorksheet: TsBasicWorksheet; ARange: TsCellRange);
procedure ReadCFAverage(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange; AFormatIndex: Integer);
procedure ReadCFCellFormat(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange; AFormatIndex: Integer);
procedure ReadCFColorRange(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange);
procedure ReadCFDataBars(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange);
procedure ReadCFMisc(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange; AFormatIndex: Integer);
procedure ReadCFTop10(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange; AFormatIndex: Integer);
procedure ReadColRowBreaks(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
function ReadColor(ANode: TDOMNode): TsColor;
procedure ReadCols(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
@ -470,6 +481,24 @@ begin
Result := lsThin;
end;
function StrToCFValueKind(s: String): TsCFValueKind;
begin
case s of
'min', 'automin':
Result := vkMin;
'max', 'automax':
Result := vkMax;
'percent':
Result := vkPercent;
'percentile':
Result := vkPercentile;
'num':
Result := vkValue;
else
Result := vkMin;
end;
end;
function CFOperandToStr(v: Variant): String;
const
ERR = cardinal(-1);
@ -1235,8 +1264,43 @@ begin
end;
end;
procedure TsSpreadOOXMLReader.ReadCFRule(ANode: TDOMNode; AWorksheet: TsBasicWorksheet;
ARange: TsCellRange);
procedure TsSpreadOOXMLReader.ReadCFAverage(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer);
var
s, sEquAve, sAboveAve: String;
condition: TsCFCondition;
stdDev: Double;
sheet: TsWorksheet;
begin
sheet := TsWorksheet(AWorksheet);
s := GetAttrValue(ANode, 'stdDev');
if not TryStrToFloat(s, stdDev, FPointSeparatorSettings) then
stdDev := 0.0;
sEquAve := GetAttrValue(ANode, 'equalAverage');
sAboveAve := GetAttrValue(ANode, 'aboveAverage');
if (sEquAve='1') then
begin
if (sAboveAve='0') then
condition := cfcBelowEqualAverage
else
condition := cfcAboveEqualAverage;
end else
begin
if (sAboveAve = '0') then
condition := cfcBelowAverage
else
condition := cfcAboveAverage;
end;
sheet.WriteConditionalCellFormat(ARange, condition, stdDev, AFormatIndex);
end;
procedure TsSpreadOOXMLReader.ReadCFCellFormat(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer);
var
nodeName: String;
s, sType, sOp: String;
@ -1247,47 +1311,25 @@ var
n: Integer;
x: Double;
r, c: Cardinal;
dxf: TDifferentialFormatData;
dxfId: Integer;
condition: TsCFCondition;
values: array of Variant;
fmtIdx: Integer;
sheet: TsWorksheet;
begin
sheet := TsWorksheet(AWorksheet);
found := false;
sType := GetAttrValue(ANode, 'type');
sOp := GetAttrValue(ANode, 'operator');
if sType = 'cellIs' then
begin
for cf in TsCFCondition do
if sOp = CF_OPERATOR_NAMES[cf] then
begin
found := true;
condition := cf;
break;
end
end
else
for cf in TsCFCondition do
if sType = CF_TYPE_NAMES[cf] then
begin
found := true;
condition := cf;
break;
end;
if not found then
found := false;
for cf in TsCFCondition do
if sOp = CF_OPERATOR_NAMES[cf] then
begin
found := true;
condition := cf;
break;
end;
if (not found) or not (condition in [cfcEqual..cfcNotBetween]) then
exit;
s := GetAttrValue(ANode, 'dxfId');
if not TryStrToInt(s, dxfId) then
exit;
dxf := TDifferentialFormatData(FDifferentialFormatList[dxfId]);
fmtIdx := dxf.CellFormatIndex;
// Process cases related to (un)equality of cells
SetLength(sFormula, 0);
ANode := ANode.FirstChild;
while (ANode <> nil) do
@ -1301,34 +1343,229 @@ begin
ANode := ANode.NextSibling;
end;
if condition in [cfcEqual..cfcNotBetween] then begin
SetLength(values, Length(sFormula));
for i := 0 to High(sFormula) do begin
values[i] := sFormula[i];
if (sFormula[i] <> '') then begin
if TryStrToInt(sFormula[i], n) then
values[i] := n
else if TryStrToFloat(sFormula[i], x, FPointSeparatorSettings) then
values[i] := x
else if sFormula[i][1] = '"' then
values[i] := sFormula[i]
else if ParseCellString(sFormula[i], r, c) then
values[i] := sFormula[i]
else
values[i] := '=' + sFormula[i];
end;
SetLength(values, Length(sFormula));
for i := 0 to High(sFormula) do begin
values[i] := sFormula[i];
if (sFormula[i] <> '') then begin
if TryStrToInt(sFormula[i], n) then
values[i] := n
else if TryStrToFloat(sFormula[i], x, FPointSeparatorSettings) then
values[i] := x
else if sFormula[i][1] = '"' then
values[i] := sFormula[i]
else if ParseCellString(sFormula[i], r, c) then
values[i] := sFormula[i]
else
values[i] := '=' + sFormula[i];
end;
end else
exit;
end;
case Length(values) of
0: sheet.WriteConditionalCellFormat(ARange, condition, fmtIdx);
1: sheet.WriteConditionalCellFormat(ARange, condition, values[0], fmtIdx);
2: sheet.WriteConditionalCellFormat(ARange, condition, values[0], values[1], fmtIdx);
0: sheet.WriteConditionalCellFormat(ARange, condition, AFormatIndex);
1: sheet.WriteConditionalCellFormat(ARange, condition, values[0], AFormatIndex);
2: sheet.WriteConditionalCellFormat(ARange, condition, values[0], values[1], AFormatIndex);
end;
end;
procedure TsSpreadOOXMLReader.ReadCFColorRange(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCellRange);
{ <colorScale>
<cfvo type="num" val="2" />
<cfvo type="percentile" val="50" />
<cfvo type="max" />
<color rgb="FF63BE7B" />
<color rgb="FFFFEB84" />
<color rgb="FFF8696B" />
</colorScale>
}
var
sheet: TsWorksheet;
nodeName, s: String;
vk: array[0..2] of TsCFValueKind = (vkNone, vkNone, vkNone);
v: array[0..2] of Double = (NaN, NaN, NaN);
c: array[0..2] of TsColor = (scNotDefined, scNotDefined, scNotDefined);
x: Double;
iv, ic: Integer;
clevels, vlevels: Integer;
begin
iv := 0;
ic := 0;
ANode := ANode.FirstChild;
if (ANode <> nil) and (ANode.NodeName = 'colorScale') then
ANode := ANode.FirstChild;
while (ANode <> nil) do
begin
nodeName := ANode.NodeName;
if (nodeName = 'color') and (ic <= High(c)) then
begin
c[ic] := ReadColor(ANode);
inc(ic);
end;
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);
end;
ANode := ANode.NextSibling;
end;
clevels := 0;
for ic := 0 to High(c) do
begin
if c[ic] = scNotDefined then
break;
inc(cLevels);
end;
vlevels := 0;
for iv := 0 to High(vk) do
begin
if vk[iv] = vkNone then
break;
inc(vlevels);
end;
// Not 100% sure, but I guess there must be as many colors as value kinds.
if vlevels <> cLevels then
begin
FWorkbook.AddErrorMsg('ColorRange: colors-levels mismatch');
exit;
end;
sheet := TsWorksheet(AWorksheet);
case clevels of
1: exit;
2: sheet.WriteColorRange(ARange, c[0],vk[0],v[0], c[1],vk[1],v[1]);
3: sheet.WriteColorRange(ARange, c[0],vk[0],v[0], c[1],vk[1],v[1], c[2],vk[2],v[2]);
end;
end;
procedure TsSpreadOOXMLReader.ReadCFDataBars(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCelLRange);
{ <dataBar>
<cfvo type="num" val="2" />
<cfvo type="percent" val="80" />
<color rgb="FFFF0000" />
</dataBar> } // We do not support the x14 namespace extension ATM
var
sheet: TsWorksheet;
nodeName, s: String;
vk: array[0..1] of TsCFValuekind;
v: array[0..1] of Double;
x: Double;
idx: Integer;
clr: TsColor;
begin
idx := 0;
ANode := ANode.FirstChild;
if (ANode <> nil) and (ANode.NodeName = 'dataBar') then
ANode := ANode.FirstChild;
while ANode <> nil do
begin
nodeName := ANode.NodeName;
if nodeName = 'color' then
clr := ReadColor(ANode)
else
clr := scBlue;
if (nodeName = 'cfvo') and (idx <= 1) then
begin
s := GetAttrValue(ANode, 'type');
vk[idx] := StrToCFValueKind(s);
s := GetAttrValue(ANode, 'val');
if TryStrToFloat(s, x, FPointSeparatorSettings) then
v[idx] := x
else
v[idx] := NaN;
inc(idx);
end;
ANode := ANode.NextSibling;
end;
// Check for value attribute. If not existing, use simple min/max scaling and log error.
if ((vk[0] in [vkPercent, vkPercentile, vkValue]) and IsNaN(v[0])) or
((vk[1] in [vkPercent, vkPercentile, vkValue]) and IsNaN(v[1])) then
begin
FWorkbook.AddErrorMsg('DataBars: value needed.');
vk[0] := vkMin;
vk[1] := vkMax;
end;
sheet := TsWorksheet(AWorksheet);
sheet.WriteDataBars(ARange, clr, vk[0], v[0], vk[1], v[1]);
end;
procedure TsSpreadOOXMLReader.ReadCFMisc(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer);
var
sheet: TsWorksheet;
s: String;
cf, condition: TsCFCondition;
found: Boolean;
begin
sheet := TsWorksheet(AWorksheet);
found := false;
s := GetAttrValue(ANode, 'type');
for cf in TsCFCondition do
if CF_TYPE_NAMES[cf] = s then
begin
condition := cf;
found := true;
break;
end;
if not found then
exit;
if condition in [cfcAboveAverage..cfcBottomPercent] then
begin
s := GetAttrValue(ANode, 'text');
sheet. WriteConditionalCellFormat(ARange, condition, s, AFormatIndex);
end else
sheet. WriteConditionalCellFormat(ARange, condition, AFormatIndex);
end;
procedure TsSpreadOOXMLReader.ReadCFTop10(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet; ARange: TsCellRange; AFormatIndex: Integer);
var
sheet: TsWorksheet;
rank: Integer;
s, sPercent, sBottom: String;
condition: TsCFCondition;
begin
sheet := TsWorksheet(AWorksheet);
s := GetAttrValue(ANode, 'rank');
if not TryStrToInt(s, rank) then
rank := 10;
sPercent := GetAttrValue(ANode, 'percent');
sBottom := GetAttrValue(ANode, 'bottom');
if (sBottom = '1') then
begin
if sPercent = '1' then
condition := cfcBottomPercent
else
condition := cfcBottom;
end else
begin
if sPercent = '1' then
condition := cfcTopPercent
else
condition := cfcTop;
end;
sheet.WriteConditionalCellFormat(ARange, condition, rank, AFormatIndex);
end;
procedure TsSpreadOOXMLReader.ReadColRowBreaks(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet);
@ -1576,6 +1813,9 @@ var
nodeName: string;
range: TsCellRange;
s: String;
dxf: TDifferentialFormatData;
dxfId: Integer;
fmtIdx: Integer;
begin
while ANode <> nil do
begin
@ -1590,7 +1830,34 @@ begin
begin
nodeName := childNode.NodeName;
if nodeName = 'cfRule' then
ReadCFRule(childNode, AWorksheet, range);
begin
// Get format index
s := GetAttrValue(childNode, 'dxfId');
if TryStrToInt(s, dxfId) then
begin
dxf := TDifferentialFormatData(FDifferentialFormatList[dxfId]);
fmtIdx := dxf.CellFormatIndex;
end else
fmtIdx := 0;
s := GetAttrValue(childNode, 'type');
case s of
'cellIs':
ReadCFCellFormat(childNode, AWorksheet, range, fmtIdx);
'aboveAverage':
ReadCFAverage(childNode, AWorksheet, range, fmtIdx);
'top10':
ReadCFTop10(childNode, AWorksheet, range, fmtIdx);
'unique', 'duplicate', 'containsErrors', 'notContainsErrors':
ReadCFMisc(childNode, AWorksheet, range, fmtIdx);
'containsText', 'notContainsText', 'beginsWith', 'endsWith':
ReadCFMisc(childNode, AWorksheet, range, fmtIdx);
'colorScale':
ReadCFColorRange(childNode, AWorksheet, range);
'dataBar':
ReadCFDataBars(childNode, AWorksheet, range);
end;
end;
childNode := childNode.NextSibling;
end;
end;
@ -3920,7 +4187,7 @@ begin
AppendToStream(AStream,
CF_ValueNode(ARule.StartValueKind, ARule.StartValue),
CF_ValueNode(ARule.EndValueKind, ARule.EndValue),
CF_ColorNode(ARule.BarColor) );
CF_ColorNode(ARule.Color) );
AppendToStream(AStream,
'</dataBar>' +