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:
wp_xxyyzz
2014-06-18 21:54:29 +00:00
parent 26d5923673
commit 9faf94ee3a
3 changed files with 183 additions and 5 deletions

View File

@ -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

View File

@ -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;
{ {

View File

@ -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")