fpspreadsheet: Some progress in writing conditional formatting to ODS files.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7506 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2020-06-30 23:04:52 +00:00
parent 23003e75b3
commit 1845d0fd8d

View File

@ -220,7 +220,7 @@ type
function WriteBorderStyleXMLAsString(const AFormat: TsCellFormat): String; function WriteBorderStyleXMLAsString(const AFormat: TsCellFormat): String;
function WriteCellProtectionStyleXMLAsString(const AFormat: TsCellFormat): String; function WriteCellProtectionStyleXMLAsString(const AFormat: TsCellFormat): String;
function WriteCommentXMLAsString(AComment: String): String; function WriteCommentXMLAsString(AComment: String): String;
function WriteConditionalStyleXMLAsString(AFormatIndex: Integer): String; function WriteConditionalStyleXMLAsString(ACFIndex: Integer): String;
function WriteDefaultFontXMLAsString: String; function WriteDefaultFontXMLAsString: String;
function WriteDefaultGraphicStyleXMLAsString: String; overload; function WriteDefaultGraphicStyleXMLAsString: String; overload;
function WriteDocumentProtectionXMLAsString: String; function WriteDocumentProtectionXMLAsString: String;
@ -383,6 +383,16 @@ const
COLWIDTH_EPS = 1e-3; COLWIDTH_EPS = 1e-3;
ROWHEIGHT_EPS = 1e-3; ROWHEIGHT_EPS = 1e-3;
CF_OPERATORS: array[TsCFCondition] of string = (
'=', '!=', '>', '<', '>=', '<=',
'', '', // cfcBetween, cfcNotBetween,
'', '', '', '', // cfcAboveAverage, cfcBelowAverage, cfcAboveEqualAverage, cfcBelowEqualAverage
'', '', '', '', // cfcTop, cfcBottom, cfcTopPercent, cfcBottomPercent,
'', '', // cfcDuplicate, cfcUnique,
'', '', '', '', // cfcBeginsWith, cfcEndsWith, cfcContainsText, cfcNotContainsText,
'', '' // cfcContainsErrors, cfcNotContainsErrors
);
type type
{ Table style items stored in TableStyleList of the reader } { Table style items stored in TableStyleList of the reader }
@ -4818,11 +4828,15 @@ function TsSpreadOpenDocWriter.GetStyleName(ACell: PCell): String;
var var
fmt: TsCellFormat; fmt: TsCellFormat;
ncf: Integer; ncf: Integer;
cf: Integer;
begin begin
ncf := Length(ACell^.ConditionalFormatIndex); ncf := Length(ACell^.ConditionalFormatIndex);
if ncf > 0 then if ncf > 0 then
Result := Format('ce%d_%d', [ACell^.FormatIndex, ACell^.ConditionalFormatIndex[ncf-1]]) begin
else cf := ACell^.ConditionalFormatIndex[ncf-1];
// Support only last conditional format of cell (having highest priority)
Result := 'ce' + IntToStr((cf+1)*1000 + ACell^.FormatIndex);
end else
begin begin
fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex); fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex);
if fmt.UsedFormattingFields <> [] then if fmt.UsedFormattingFields <> [] then
@ -5468,6 +5482,7 @@ begin
'</table:table>'); '</table:table>');
end; end;
{ Writes the style node in "content.xml" }
procedure TsSpreadOpenDocWriter.WriteCellStyle(AStream: TStream; procedure TsSpreadOpenDocWriter.WriteCellStyle(AStream: TStream;
AFormatIndex, AConditionalFormatIndex: integer); AFormatIndex, AConditionalFormatIndex: integer);
var var
@ -5488,11 +5503,12 @@ begin
// The style name will be 'ce' plus format index in the workbook's CellFormats // The style name will be 'ce' plus format index in the workbook's CellFormats
// list. // list.
styleName := 'ce' + IntToStr(AFormatIndex); // In case of a conditional format the style name is a combination of
// In case of a conditional format the index in the worksheet's // conditional format index and normal format index.
// ConditionalFormatList will be added after an underscore character.
if isConditionalFormat then if isConditionalFormat then
styleName := styleName + '_' + IntToStr(AConditionalFormatIndex); styleName := 'ce' + IntToStr(1000 * (AConditionalFormatIndex+1) + AFormatIndex)
else
styleName := 'ce' + IntToStr(AFormatIndex);
fmt := book.GetCellFormat(AFormatIndex); fmt := book.GetCellFormat(AFormatIndex);
@ -5575,7 +5591,7 @@ var
cf_range: TsCellRange; cf_range: TsCellRange;
cf_rule: TsCFCellRule; cf_rule: TsCFCellRule;
ncf: Integer; ncf: Integer;
i, j, k, p: Integer; i, j: Integer;
cell: PCell; cell: PCell;
r, c: Cardinal; r, c: Cardinal;
L: TStrings; L: TStrings;
@ -5609,7 +5625,7 @@ begin
if Assigned(cell) and if Assigned(cell) and
(cell^.ConditionalFormatIndex[High(cell^.ConditionalFormatIndex)] = i) (cell^.ConditionalFormatIndex[High(cell^.ConditionalFormatIndex)] = i)
then begin then begin
s := Format('ce%d_%d', [cell^.FormatIndex, i]); s := 'ce' + IntToStr((i+1) * 1000 + cell^.FormatIndex);
if L.IndexOf(s) = -1 then if L.IndexOf(s) = -1 then
L.Add(s); L.Add(s);
end; end;
@ -5619,11 +5635,10 @@ begin
// Now write the combined styles to the stream. The styles can be identified // Now write the combined styles to the stream. The styles can be identified
// from the style name in the string list. // from the style name in the string list.
for i := 0 to L.Count-1 do begin for i := 0 to L.Count-1 do begin
s := L[i]; s := Copy(L[i], 3, MaxInt); // remove 'ce'
p := pos('_', L[i]); j := StrToInt(s);
fmtIndex := StrToInt(Copy(L[i], 3, p-3)); DivMod(j, 1000, cfIndex, fmtIndex);
cfIndex := StrToInt(Copy(L[i], p+1, MaxInt)); WriteCellStyle(AStream, fmtIndex, cfIndex-1);
WriteCellStyle(AStream, fmtIndex, cfIndex);
end; end;
finally finally
L.Free; L.Free;
@ -5847,76 +5862,82 @@ var
cf_range: TsCellRange; cf_range: TsCellRange;
cf_styleName: String; cf_styleName: String;
cf_cellRule: TsCFCellRule; cf_cellRule: TsCFCellRule;
i, j: Integer; i, j, k: Integer;
sheet: TsWorksheet; sheet: TsWorksheet;
rangeStr: String; rangeStr: String;
firstCellStr: string; firstCellStr: string;
value1Str, value2Str: String; value1Str, value2Str: String;
opStr: String;
s: String; s: String;
begin begin
book := TsWorkbook(FWorkbook); book := TsWorkbook(FWorkbook);
sheet := TsWorksheet(ASheet); sheet := TsWorksheet(ASheet);
ncf := book.GetNumConditionalFormats; ncf := book.GetNumConditionalFormats;
AppendToStream(AStream,
'<calcext:conditional-formats>');
for i := 0 to ncf-1 do begin for i := 0 to ncf-1 do begin
cf := book.GetConditionalFormat(i); cf := book.GetConditionalFormat(i);
if cf.Worksheet <> ASheet then if cf.Worksheet <> ASheet then
continue; continue;
cf_styleName := 'cf' + IntToStr(i);
cf_range := cf.CellRange; cf_range := cf.CellRange;
firstCellStr := sheet.Name + '.' + GetCellString(cf_range.Row1, cf_range.Col1); firstCellStr := sheet.Name + '.' + GetCellString(cf_range.Row1, cf_range.Col1);
rangeStr := firstCellStr + ':' + sheet.Name + '.' + GetCellString(cf_range.Row2, cf_range.Col2); rangeStr := firstCellStr + ':' + sheet.Name + '.' + GetCellString(cf_range.Row2, cf_range.Col2);
if cf.Rules[cf.RulesCount-1] is TsCFCellRule then
AppendToStream(AStream, Format(
'<calcext:conditional-format calcext:target-range-address="%s">', [
rangeStr
]));
for k := 0 to cf.RulesCount-1 do
begin begin
cf_cellRule := TsCFCellRule(cf.Rules[cf.RulesCount-1]); value1Str := '';
case cf_cellRule.Condition of value2Str := '';
cfcEqual: opStr := '';
if VarIsStr(cf_cellRule.Operand1) then if cf.Rules[k] is TsCFCellRule then
value1Str := '=' + UTF8TextToXMLText(SafeQuoteStr(cf_cellrule.Operand1))
else
value1Str := '=' + VarToStr(cf_cellRule.Operand1);
else
Continue;
end;
s := Format(
'<calcext:conditional-formats>' +
'<calcext:conditional-format calcext:target-range-address="%s">' +
'<calcext:condition calcext:apply-style-name="%s" calcext:value="%s" calcext:base-cell-address="%s" />' +
'</calcext:conditional-format>' +
'</calcext:conditional-formats>', [
rangeStr, cf_stylename, value1Str, firstCellStr
]);
AppendToStream(AStream, s);
end;
(*
for j := 0 to cf.RulesCount-1 do
begin
range := cf.CellRange;
firstCellStr := sheet.Name + '.' + GetCellString(range.Row1, range.Col1);
rangeStr := firstCellStr + ':' + sheet.Name + '.' + GetCellString(range.Row2, range.Col2);
if cf.Rules[j] is TsCFCellRule then
begin begin
cellRule := TsCFCellRule(cf.Rules[j]); cf_cellRule := TsCFCellRule(cf.Rules[k]);
cf_styleName := Format('cf%d_%d', [sheet.Index, cellRule.FormatIndex]); cf_styleName := Format('conditional_%d', [cf_CellRule.FormatIndex]);
case cellRule.Condition of case cf_cellRule.Condition of
cfcEqual: value1Str := '=' + VarToStr(cellRule.Operand1); cfcEqual..cfcLessEqual:
else Continue; begin
opStr := CF_OPERATORS[cf_cellRule.Condition];
if VarIsStr(cf_cellRule.Operand1) then
value1Str := UTF8TextToXMLText(SafeQuoteStr(cf_cellrule.Operand1))
else
value1Str := VarToStr(cf_cellRule.Operand1);
opStr := CF_OPERATORS[cf_cellRule.Condition] + value1Str;
end;
cfcBetween, cfcNotBetween:
begin
if VarIsStr(cf_cellRule.Operand1) then
value1Str := UTF8TextToXMLText(SafeQuoteStr(cf_cellrule.Operand1))
else
value1Str := VarToStr(cf_cellRule.Operand1);
if VarIsStr(cf_cellRule.Operand2) then
value2Str := UTF8TextToXMLText(SafeQuoteStr(cf_cellrule.Operand2))
else
value2Str := VarToStr(cf_cellRule.Operand2);
opStr := Format('between(%s,%s)', [value1Str, value2Str]);
if cf_cellRule.Condition = cfcNotBetween then
opStr := 'not-' + opStr;
end;
else
Continue;
end; end;
s := Format( AppendToStream(AStream, Format(
'<calcext:conditional-formats>' + '<calcext:condition calcext:apply-style-name="%s" calcext:value="%s" calcext:base-cell-address="%s" />',
'<calcext:conditional-format calcext:target-range-address="%s">' + [cf_stylename, opStr, firstCellStr]
'<calcext:condition calcext:apply-style-name="%s" calcext:value="%s" calcext:base-cell-address="%s" />' + ));
'</calcext:conditional-format>' +
'</calcext:conditional-formats>', [
rangeStr, cf_stylename, value1Str, firstCellStr
]);
AppendToStream(AStream, s);
end; end;
end; end;
*)
AppendToStream(AStream,
'</calcext:conditional-format>');
end; end;
AppendToStream(AStream,
'</calcext:conditional-formats>' );
end; end;
{ Writes the conditional format part of a style to "styles.xml". } { Writes the conditional format part of a style to "styles.xml". }
@ -5954,7 +5975,9 @@ var
nCF: Integer; nCF: Integer;
CF: TsConditionalFormat; CF: TsConditionalFormat;
fmt: TsCellFormat; fmt: TsCellFormat;
cf_rule: TsCFCellRule; fmtIndex: Integer;
cf_rule: TsCFRule;
stylename: String;
begin begin
book := TsWorkbook(FWorkbook); book := TsWorkbook(FWorkbook);
nCF := book.GetNumConditionalFormats; nCF := book.GetNumConditionalFormats;
@ -5962,6 +5985,20 @@ begin
for i := 0 to nCF-1 do for i := 0 to nCF-1 do
begin begin
CF := book.GetConditionalFormat(i); CF := book.GetConditionalFormat(i);
for j := 0 to CF.RulesCount-1 do
begin
cf_Rule := CF.Rules[j];
if cf_Rule is TsCFCellRule then
begin
fmtIndex := TsCFCellRule(cf_Rule).FormatIndex;
fmt := book.GetCellFormat(TsCFCellRule(cf_Rule).FormatIndex);
stylename := Format('conditional_%d', [fmtIndex]);
WriteConditionalStyle(AStream, stylename, fmt);
end;
end;
end;
(*
// for the moment: write only the style of the highest-priority rule // for the moment: write only the style of the highest-priority rule
if CF.Rules[CF.RulesCount-1] is TsCFCellRule then if CF.Rules[CF.RulesCount-1] is TsCFCellRule then
begin begin
@ -5970,6 +6007,7 @@ begin
WriteConditionalStyle(AStream, Format('cf%d', [i]), fmt); // "cf" + index of CF in book's list WriteConditionalStyle(AStream, Format('cf%d', [i]), fmt); // "cf" + index of CF in book's list
end; end;
end; end;
*)
(* (*
for i := 0 to book.GetWorksheetCount-1 do for i := 0 to book.GetWorksheetCount-1 do
begin begin
@ -7063,10 +7101,16 @@ var
r1,c1,r2,c2: Cardinal; r1,c1,r2,c2: Cardinal;
fmt: TsCellFormat; fmt: TsCellFormat;
sheet: TsWorksheet; sheet: TsWorksheet;
lStyle: String;
begin begin
Unused(ARow, ACol); Unused(ARow, ACol);
sheet := FWorksheet as TsWorksheet; sheet := FWorksheet as TsWorksheet;
// Style
lStyle := GetStyleName(ACell);
if lStyle <> '' then
lStyle := Format(' table:style-name="%s"', [lStyle]);
// Hyperlink // Hyperlink
if sheet.HasHyperlink(ACell) then if sheet.HasHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]); FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
@ -7087,8 +7131,8 @@ begin
fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex); fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex);
if (fmt.UsedFormattingFields <> []) then if (fmt.UsedFormattingFields <> []) then
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d"%s>', [ACell^.FormatIndex, spannedStr]), '<table:table-cell table:style-name="%s"%s>', [lStyle, spannedStr]),
comment, comment,
'</table:table-cell>') '</table:table-cell>')
else else
if comment <> '' then if comment <> '' then
@ -7116,6 +7160,8 @@ begin
Unused(ARow, ACol); Unused(ARow, ACol);
valType := 'boolean'; valType := 'boolean';
// Style
lStyle := GetStyleName(ACell); lStyle := GetStyleName(ACell);
if lStyle <> '' then if lStyle <> '' then
lStyle := Format(' table:style-name="%s"', [lStyle]); lStyle := Format(' table:style-name="%s"', [lStyle]);
@ -7300,49 +7346,68 @@ begin
end; end;
end; end;
function TsSpreadOpenDocWriter.WriteConditionalStyleXMLAsString(AFormatIndex: Integer): string; function TsSpreadOpenDocWriter.WriteConditionalStyleXMLAsString(ACFIndex: Integer): string;
var var
book: TsWorkbook; book: TsWorkbook;
isConditional: Boolean; k: Integer;
i, j, k: Integer;
cf: TsConditionalFormat; cf: TsConditionalFormat;
cf_CellRule: TsCFCellRule; cf_CellRule: TsCFCellRule;
cf_StyleName: String; cf_StyleName: String;
cf_Condition: String; cf_Condition: String;
cf_Sheet: TsWorksheet; cf_Sheet: TsWorksheet;
firstCellOfRange: String; firstCellOfRange: String;
operand1Str: String; operand1Str, operand2Str: String;
begin begin
Result := ''; Result := '';
book := TsWorkbook(FWorkbook); book := TsWorkbook(FWorkbook);
cf := book.GetConditionalFormat(AFormatIndex); cf := book.GetConditionalFormat(ACFIndex);
cf_styleName := 'cf' + IntToStr(AFormatIndex);
cf_sheet := cf.Worksheet as TsWorksheet; cf_sheet := cf.Worksheet as TsWorksheet;
firstCellOfRange := cf_sheet.Name + '.' + GetCellString(cf.CellRange.Row1, cf.CellRange.Col1); firstCellOfRange := cf_sheet.Name + '.' + GetCellString(cf.CellRange.Row1, cf.CellRange.Col1);
if cf.Rules[cf.RulesCount-1] is TsCFCellRule then // Every rule has a style:map node
begin for k := 0 to cf.RulesCount-1 do begin
// for the moment: we only use the highest-priority rule if cf.Rules[k] is TsCFCellRule then
cf_cellRule := TsCFCellRule(cf.Rules[cf.RulesCount-1]); begin
case cf_cellRule.Condition of cf_cellRule := TsCFCellRule(cf.Rules[k]);
cfcEqual: cf_styleName := Format('conditional_%d', [cf_cellRule.FormatIndex]);
begin case cf_cellRule.Condition of
operand1Str := VarToStr(cf_cellrule.Operand1); cfcEqual..cfcLessEqual:
if VarIsStr(cf_cellRule.Operand1) then begin
operand1Str := UTF8TextToXMLText(SafeQuoteStr(cf_cellrule.Operand1)); operand1Str := VarToStr(cf_cellrule.Operand1);
cf_Condition := Format('cell-content()=%s', [operand1Str]); if VarIsStr(cf_cellRule.Operand1) then
end; operand1Str := UTF8TextToXMLText(SafeQuoteStr(operand1Str));
else cf_Condition := Format('cell-content()%s%s', [
cf_Condition := ''; CF_OPERATORS[cf_cellRule.Condition],
end; operand1Str
]);
end;
cfcBetween, cfcNotBetween:
begin
operand1Str := VarToStr(cf_cellrule.Operand1);
if VarIsStr(cf_cellRule.Operand1) then
operand1Str := UTF8TextToXMLText(SafeQuoteStr(operand1Str));
operand2Str := VarToStr(cf_cellrule.Operand2);
if VarIsStr(cf_cellRule.Operand2) then
operand2Str := UTF8TextToXMLText(SafeQuoteStr(operand2Str));
case cf_cellRule.Condition of
cfcBetween: cf_Condition := 'cell-content-is-between(%s,%s)';
cfcNotBetween: cf_Condition := 'cell-content-is-not-between(%s,%s)';
end;
cf_Condition := Format(cf_Condition, [operand1Str, operand2Str]);
end;
else
cf_Condition := '';
end;
if cf_Condition <> '' then if cf_Condition <> '' then
Result := Format('<style:map style:condition="%s" style:apply-style-name="%s" style:base-cell-address="%s" />', [ Result := Result +
cf_Condition, Format('<style:map style:condition="%s" style:apply-style-name="%s" style:base-cell-address="%s" />', [
cf_StyleName, cf_Condition,
firstCellOfRange cf_StyleName,
]); firstCellOfRange
]);
end;
end; end;
(* (*
// Determine whether the format is a conditional format. // Determine whether the format is a conditional format.
@ -7467,11 +7532,10 @@ begin
sheet := FWorksheet as TsWorksheet; sheet := FWorksheet as TsWorksheet;
fmt := (FWorkbook as TsWorkbook).GetPointerToCellFormat(ACell^.FormatIndex); // Style
if fmt^.UsedFormattingFields <> [] then lStyle := GetStyleName(ACell);
lStyle := ' table:style-name="ce' + IntToStr(ACell^.FormatIndex) + '" ' if lStyle <> '' then
else lStyle := Format(' table:style-name="%s"', [lStyle]);
lStyle := '';
// Comment // Comment
comment := WriteCommentXMLAsString(sheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(sheet.ReadComment(ACell));
@ -8269,11 +8333,9 @@ begin
sheet := FWorksheet as TsWorksheet; sheet := FWorksheet as TsWorksheet;
// Style // Style
fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex); lStyle := GetStyleName(ACell);
if fmt.UsedFormattingFields <> [] then if lStyle <> '' then
lStyle := ' table:style-name="ce' + IntToStr(ACell^.FormatIndex) + '" ' lStyle := Format(' table:style-name="%s"', [lStyle]);
else
lStyle := '';
// Comment // Comment
comment := WriteCommentXMLAsString(sheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(sheet.ReadComment(ACell));
@ -8459,11 +8521,9 @@ begin
sheet := FWorksheet as TsWorksheet; sheet := FWorksheet as TsWorksheet;
// Style // Style
fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex); lStyle := GetStyleName(ACell);
if fmt.UsedFormattingFields <> [] then if lStyle <> '' then
lStyle := ' table:style-name="ce' + IntToStr(ACell^.FormatIndex) + '"' lStyle := Format(' table:style-name="%s"', [lStyle]);
else
lStyle := '';
// Comment // Comment
comment := WriteCommentXMLAsString(sheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(sheet.ReadComment(ACell));
@ -8615,6 +8675,7 @@ begin
Unused(ARow, ACol); Unused(ARow, ACol);
valType := 'float'; valType := 'float';
lStyle := GetStyleName(ACell); lStyle := GetStyleName(ACell);
if lStyle <> '' then if lStyle <> '' then
lStyle := Format(' table:style-name="%s"', [lStyle]); lStyle := Format(' table:style-name="%s"', [lStyle]);
@ -8714,12 +8775,13 @@ begin
end else end else
spannedStr := ''; spannedStr := '';
// Style
lStyle := GetStyleName(ACell);
if lStyle <> '' then
lStyle := Format(' table:style-name="%s"', [lStyle]);
fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex); fmt := (FWorkbook as TsWorkbook).GetCellFormat(ACell^.FormatIndex);
numFmtParams := (FWorkbook as TsWorkbook).GetNumberFormat(fmt.NumberFormatIndex); numFmtParams := (FWorkbook as TsWorkbook).GetNumberFormat(fmt.NumberFormatIndex);
if fmt.UsedFormattingFields <> [] then
lStyle := Format(' table:style-name="ce%d"', [ACell^.FormatIndex])
else
lStyle := '';
// Comment // Comment
comment := WriteCommentXMLAsString(sheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(sheet.ReadComment(ACell));