You've already forked lazarus-ccr
fpspreadsheet: Add writing of date/time formats to ods files and fix writing of time values
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3199 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -147,6 +147,7 @@ type
|
|||||||
procedure ClearAll;
|
procedure ClearAll;
|
||||||
function GetDateTimeCode(ASection: Integer): String;
|
function GetDateTimeCode(ASection: Integer): String;
|
||||||
function IsDateTimeFormat: Boolean;
|
function IsDateTimeFormat: Boolean;
|
||||||
|
function IsTimeFormat: Boolean;
|
||||||
procedure LimitDecimals;
|
procedure LimitDecimals;
|
||||||
procedure Localize;
|
procedure Localize;
|
||||||
|
|
||||||
@ -1184,6 +1185,26 @@ begin
|
|||||||
Result := false;
|
Result := false;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ Returns true if the format elements contain only time, no date tokens. }
|
||||||
|
function TsNumFormatParser.IsTimeFormat: Boolean;
|
||||||
|
var
|
||||||
|
section: Integer;
|
||||||
|
elem: Integer;
|
||||||
|
begin
|
||||||
|
Result := false;
|
||||||
|
for section := 0 to High(FSections) do
|
||||||
|
for elem := 0 to High(FSections[section].Elements) do
|
||||||
|
if FSections[section].Elements[elem].Token in [nftHour, nftMinute, nftSecond]
|
||||||
|
then begin
|
||||||
|
Result := true;
|
||||||
|
end else
|
||||||
|
if FSections[section].Elements[elem].Token in [nftYear, nftMonth, nftDay, nftExpChar, nftCurrSymbol]
|
||||||
|
then begin
|
||||||
|
Result := false;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TsNumFormatParser.IsTokenAt(AToken: TsNumFormatToken;
|
function TsNumFormatParser.IsTokenAt(AToken: TsNumFormatToken;
|
||||||
ASection, AIndex: Integer): Boolean;
|
ASection, AIndex: Integer): Boolean;
|
||||||
begin
|
begin
|
||||||
|
@ -58,6 +58,9 @@ type
|
|||||||
|
|
||||||
{ TsSpreadOpenDocNumFormatParser }
|
{ TsSpreadOpenDocNumFormatParser }
|
||||||
TsSpreadOpenDocNumFormatParser = class(TsNumFormatParser)
|
TsSpreadOpenDocNumFormatParser = class(TsNumFormatParser)
|
||||||
|
private
|
||||||
|
function BuildDateTimeXMLAsString(ASection: Integer; AIndent: String;
|
||||||
|
out AIsTimeOnly: Boolean): String;
|
||||||
protected
|
protected
|
||||||
function BuildXMLAsStringFromSection(ASection: Integer;
|
function BuildXMLAsStringFromSection(ASection: Integer;
|
||||||
AIndent,AFormatName: String): String;
|
AIndent,AFormatName: String): String;
|
||||||
@ -312,6 +315,104 @@ end;
|
|||||||
|
|
||||||
{ TsSpreadOpenDocNumFormatParser }
|
{ TsSpreadOpenDocNumFormatParser }
|
||||||
|
|
||||||
|
function TsSpreadOpenDocNumFormatParser.BuildDateTimeXMLAsString(ASection: Integer;
|
||||||
|
AIndent: String; out AIsTimeOnly: boolean): String;
|
||||||
|
var
|
||||||
|
el: Integer;
|
||||||
|
s: String;
|
||||||
|
prevToken: TsNumFormatToken;
|
||||||
|
begin
|
||||||
|
Result := '';
|
||||||
|
AIsTimeOnly := true;
|
||||||
|
with FSections[ASection] do begin
|
||||||
|
el := 0;
|
||||||
|
while el < Length(Elements) do begin
|
||||||
|
case Elements[el].Token of
|
||||||
|
nftYear:
|
||||||
|
begin
|
||||||
|
prevToken := Elements[el].Token;
|
||||||
|
AIsTimeOnly := false;
|
||||||
|
s := IfThen(Elements[el].IntValue > 2, 'number:style="long" ', '');
|
||||||
|
Result := Result + AIndent +
|
||||||
|
' <number:year ' + s + '/>' + LineEnding;
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftMonth:
|
||||||
|
begin
|
||||||
|
prevToken := Elements[el].Token;
|
||||||
|
AIsTimeOnly := false;
|
||||||
|
case Elements[el].IntValue of
|
||||||
|
1: s := '';
|
||||||
|
2: s := 'number:style="long"';
|
||||||
|
3: s := 'number:textual="true"';
|
||||||
|
4: s := 'number:style="long" number:textual="true"';
|
||||||
|
end;
|
||||||
|
Result := result + AIndent +
|
||||||
|
' <number:month ' + s + '/>' + LineEnding;
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftDay:
|
||||||
|
begin
|
||||||
|
prevToken := Elements[el].Token;
|
||||||
|
AIsTimeOnly := false;
|
||||||
|
case Elements[el].IntValue of
|
||||||
|
1: s := 'day ';
|
||||||
|
2: s := 'day number:style="long"';
|
||||||
|
3: s := 'day number:textual="true"';
|
||||||
|
4: s := 'day number:style="long" number:textual="true"';
|
||||||
|
5: s := 'day-of-week ';
|
||||||
|
6: s := 'day-of-week number:style="long"';
|
||||||
|
end;
|
||||||
|
Result := Result + AIndent +
|
||||||
|
' <number:' + s + '/>' + LineEnding;
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftHour, nftMinute, nftSecond:
|
||||||
|
begin
|
||||||
|
prevToken := Elements[el].Token;
|
||||||
|
case Elements[el].Token of
|
||||||
|
nftHour : s := 'hours ';
|
||||||
|
nftMinute: s := 'minutes ';
|
||||||
|
nftSecond: s := 'seconds ';
|
||||||
|
end;
|
||||||
|
s := s + IfThen(abs(Elements[el].IntValue) = 1, '', 'number:style="long" ');
|
||||||
|
if Elements[el].IntValue < 0 then
|
||||||
|
s := s + 'number:truncate-on-overflow="false" ';
|
||||||
|
Result := Result + AIndent +
|
||||||
|
' <number:' + s + '/>' + LineEnding;
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftMilliseconds:
|
||||||
|
begin
|
||||||
|
// ???
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftDateTimeSep, nftText, nftEscaped, nftSpace:
|
||||||
|
begin
|
||||||
|
if Elements[el].TextValue = ' ' then
|
||||||
|
s := '<![CDATA[ ]]>'
|
||||||
|
else begin
|
||||||
|
s := Elements[el].TextValue;
|
||||||
|
if (s = '/') then begin
|
||||||
|
if prevToken in [nftYear, nftMonth, nftDay] then
|
||||||
|
s := FWorkbook.FormatSettings.DateSeparator
|
||||||
|
else
|
||||||
|
s := FWorkbook.FormatSettings.TimeSeparator;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Result := Result + AIndent +
|
||||||
|
' <number:text>' + s + '</number:text>' + LineEnding;
|
||||||
|
end;
|
||||||
|
|
||||||
|
nftAMPM:
|
||||||
|
Result := Result + AIndent +
|
||||||
|
' <number:am-pm />' + LineEnding;
|
||||||
|
end;
|
||||||
|
inc(el);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TsSpreadOpenDocNumFormatParser.BuildXMLAsString(AIndent,
|
function TsSpreadOpenDocNumFormatParser.BuildXMLAsString(AIndent,
|
||||||
AFormatName: String): String;
|
AFormatName: String): String;
|
||||||
var
|
var
|
||||||
@ -340,6 +441,7 @@ var
|
|||||||
clr: TsColorvalue;
|
clr: TsColorvalue;
|
||||||
el: Integer;
|
el: Integer;
|
||||||
s: String;
|
s: String;
|
||||||
|
isTimeOnly: Boolean;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
Result := '';
|
Result := '';
|
||||||
@ -528,6 +630,23 @@ begin
|
|||||||
end; // while
|
end; // while
|
||||||
Result := Result + sStyleMap + AIndent + '</number:currency-style>' + LineEnding;
|
Result := Result + sStyleMap + AIndent + '</number:currency-style>' + LineEnding;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// date/time
|
||||||
|
nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond:
|
||||||
|
begin
|
||||||
|
s := BuildDateTimeXMLAsString(ASection, AIndent, isTimeOnly);
|
||||||
|
if isTimeOnly then
|
||||||
|
Result := Result + AIndent +
|
||||||
|
'<number:time-style style:name="' + AFormatName + '">' + LineEnding +
|
||||||
|
s + AIndent +
|
||||||
|
'</number:time-style>' + LineEnding
|
||||||
|
else
|
||||||
|
Result := Result + AIndent +
|
||||||
|
'<number:date-style style:name="' + AFormatName + '">' + LineEnding +
|
||||||
|
s + AIndent +
|
||||||
|
'</number:date-style>' + LineEnding;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
inc(el);
|
inc(el);
|
||||||
end;
|
end;
|
||||||
@ -3035,9 +3154,16 @@ end;
|
|||||||
*******************************************************************}
|
*******************************************************************}
|
||||||
procedure TsSpreadOpenDocWriter.WriteDateTime(AStream: TStream;
|
procedure TsSpreadOpenDocWriter.WriteDateTime(AStream: TStream;
|
||||||
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
||||||
|
const
|
||||||
|
FMT: array[boolean] of string = (ISO8601FormatExtended, ISO8601FormatTimeOnly);
|
||||||
|
DT: array[boolean] of string = ('date', 'time');
|
||||||
|
// Index "boolean" is to be understood as "isTimeOnly"
|
||||||
var
|
var
|
||||||
lStyle: string = '';
|
lStyle: string;
|
||||||
|
strValue: String;
|
||||||
|
displayStr: String;
|
||||||
lIndex: Integer;
|
lIndex: Integer;
|
||||||
|
isTimeOnly: Boolean;
|
||||||
begin
|
begin
|
||||||
if ACell^.UsedFormattingFields <> [] then begin
|
if ACell^.UsedFormattingFields <> [] then begin
|
||||||
lIndex := FindFormattingInList(ACell);
|
lIndex := FindFormattingInList(ACell);
|
||||||
@ -3046,9 +3172,18 @@ begin
|
|||||||
lStyle := '';
|
lStyle := '';
|
||||||
|
|
||||||
// The row should already be the correct one
|
// The row should already be the correct one
|
||||||
FCellContent :=
|
|
||||||
' <table:table-cell office:value-type="date" office:date-value="' + FormatDateTime(ISO8601FormatExtended, AValue) + '"' + lStyle + '>' + LineEnding +
|
// We have to distinguish between time-only values and values that contain date parts.
|
||||||
' </table:table-cell>' + LineEnding;
|
isTimeOnly := IsTimeFormat(ACell^.NumberFormat) or IsTimeFormat(ACell^.NumberFormatStr);
|
||||||
|
strValue := FormatDateTime(FMT[isTimeOnly], AValue);
|
||||||
|
displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue);
|
||||||
|
|
||||||
|
FCellContent := Format(
|
||||||
|
' <table:table-cell office:value-type="%s" office:%s-value="%s" %s>' + LineEnding +
|
||||||
|
' <text:p>%s</text:p> ' + LineEnding +
|
||||||
|
' </table:table-cell>' + LineEnding, [
|
||||||
|
DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, displayStr
|
||||||
|
]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -29,6 +29,8 @@ const
|
|||||||
ISO8601Format='yyyymmdd"T"hhmmss';
|
ISO8601Format='yyyymmdd"T"hhmmss';
|
||||||
// Extended ISO 8601 date/time format, used in e.g. ODF/opendocument
|
// Extended ISO 8601 date/time format, used in e.g. ODF/opendocument
|
||||||
ISO8601FormatExtended='yyyy"-"mm"-"dd"T"hh":"mm":"ss';
|
ISO8601FormatExtended='yyyy"-"mm"-"dd"T"hh":"mm":"ss';
|
||||||
|
// ISO 8601 time-only format, used in ODF/opendocument
|
||||||
|
ISO8601FormatTimeOnly='"PT"hh"H"nn"M"ss"S"';
|
||||||
|
|
||||||
// Endianess helper functions
|
// Endianess helper functions
|
||||||
function WordToLE(AValue: Word): Word;
|
function WordToLE(AValue: Word): Word;
|
||||||
@ -71,6 +73,8 @@ function IfThen(ACondition: Boolean; AValue1,AValue2: TsNumberFormat): TsNumberF
|
|||||||
function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean;
|
function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean;
|
||||||
function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
|
function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
|
||||||
function IsDateTimeFormat(AFormatStr: String): Boolean; overload;
|
function IsDateTimeFormat(AFormatStr: String): Boolean; overload;
|
||||||
|
function IsTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
|
||||||
|
function IsTimeFormat(AFormatStr: String): Boolean; overload;
|
||||||
|
|
||||||
function BuildCurrencyFormatString(ADialect: TsNumFormatDialect;
|
function BuildCurrencyFormatString(ADialect: TsNumFormatDialect;
|
||||||
ANumberFormat: TsNumberFormat; const AFormatSettings: TFormatSettings;
|
ANumberFormat: TsNumberFormat; const AFormatSettings: TFormatSettings;
|
||||||
@ -579,6 +583,24 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function IsTimeFormat(AFormat: TsNumberFormat): boolean;
|
||||||
|
begin
|
||||||
|
Result := AFormat in [nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM,
|
||||||
|
nfTimeInterval];
|
||||||
|
end;
|
||||||
|
|
||||||
|
function IsTimeFormat(AFormatStr: String): Boolean;
|
||||||
|
var
|
||||||
|
parser: TsNumFormatParser;
|
||||||
|
begin
|
||||||
|
parser := TsNumFormatParser.Create(nil, AFormatStr);
|
||||||
|
try
|
||||||
|
Result := parser.IsTimeFormat;
|
||||||
|
finally
|
||||||
|
parser.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ Builds a date/time format string from the numberformat code. If the format code
|
{ Builds a date/time format string from the numberformat code. If the format code
|
||||||
is nfFmtDateTime the given AFormatString is used. AFormatString can use the
|
is nfFmtDateTime the given AFormatString is used. AFormatString can use the
|
||||||
abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss")
|
abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss")
|
||||||
|
Reference in New Issue
Block a user