fpspreadsheet: Apply cell style and number format for ods date/time cells. Still some issues in unit test.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3130 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-06-02 22:07:40 +00:00
parent 8fd1b142ab
commit 56a39e6da2

View File

@ -70,6 +70,7 @@ type
procedure ApplyColWidths;
// Applies a style to a cell
procedure ApplyStyleToCell(ARow, ACol: Cardinal; AStyleName: String);
function ExtractDateTimeFromNode(ANode: TDOMNode): TDateTime;
// Searches a style by its name in the StyleList
function FindCellStyleByName(AStyleName: String): integer;
// Searches a column style by its column index or its name in the StyleList
@ -91,10 +92,10 @@ type
procedure ReadStyles(AStylesNode: TDOMNode);
{ Record writing methods }
procedure ReadBlank(ARow, ACol: Word; ACellNode: TDOMNode);
procedure ReadDateTime(ARow : Word; ACol : Word; ACellNode: TDOMNode);
procedure ReadFormula(ARow : Word; ACol : Word; ACellNode: TDOMNode);
procedure ReadLabel(ARow : Word; ACol : Word; ACellNode: TDOMNode);
procedure ReadNumber(ARow : Word; ACol : Word; ACellNode: TDOMNode);
procedure ReadDate(ARow : Word; ACol : Word; ACellNode: TDOMNode);
public
{ General reading methods }
constructor Create(AWorkbook: TsWorkbook); override;
@ -433,7 +434,7 @@ begin
// Number format
if styleData.NumFormatIndex > -1 then
if cell^.ContentType = cctNumber then begin
if (cell^.ContentType in [cctNumber, cctDateTime]) then begin
numFmtData := NumFormatList[styleData.NumFormatIndex];
if numFmtData <> nil then begin
Include(cell^.UsedFormattingFields, uffNumberFormat);
@ -453,6 +454,80 @@ begin
FNumFormatList := TsSpreadOpenDocNumFormatList.Create(Workbook);
end;
{ Extracts a date/time value from a "date-value" or "time-value" cell node.
Is called from "ReadDateTime". }
function TsSpreadOpenDocReader.ExtractDateTimeFromNode(ANode: TDOMNode): TDateTime;
var
Value: String;
Fmt : TFormatSettings;
FoundPos : integer;
Hours, Minutes, Seconds: integer;
HoursPos, MinutesPos, SecondsPos: integer;
begin
// Format expects ISO 8601 type date string or
// time string
fmt := DefaultFormatSettings;
fmt.ShortDateFormat := 'yyyy-mm-dd';
fmt.DateSeparator := '-';
fmt.LongTimeFormat := 'hh:nn:ss';
fmt.TimeSeparator := ':';
Value := GetAttrValue(ANode, 'office:date-value');
if Value <> '' then begin
// Date or date/time string
Value := StringReplace(Value,'T',' ',[rfIgnoreCase,rfReplaceAll]);
// Strip milliseconds?
FoundPos := Pos('.',Value);
if (FoundPos > 1) then
Value := Copy(Value, 1, FoundPos-1);
Result := StrToDateTime(Value, Fmt);
end else begin
// Try time only, e.g. PT23H59M59S
// 12345678901
Value := GetAttrValue(ANode, 'office:time-value');
if (Value <> '') and (Pos('PT', Value) = 1) then begin
// Get hours
HoursPos := Pos('H', Value);
if (HoursPos > 0) then
Hours := StrToInt(Copy(Value, 3, HoursPos-3))
else
Hours := 0;
// Get minutes
MinutesPos := Pos('M', Value);
if (MinutesPos > 0) and (MinutesPos > HoursPos) then
Minutes := StrToInt(Copy(Value, HoursPos+1, MinutesPos-HoursPos-1))
else
Minutes := 0;
// Get seconds
SecondsPos := Pos('S', Value);
if (SecondsPos > 0) and (SecondsPos > MinutesPos) then
Seconds := StrToInt(Copy(Value, MinutesPos+1, SecondsPos-MinutesPos-1))
else
Seconds := 0;
// Times smaller than a day can be taken as is
// Times larger than a day depend on the file's date mode.
// Convert to date/time via Unix timestamp so avoiding limits for number of
// hours etc in EncodeDateTime. Perhaps there's a faster way of doing this?
Result := UnixToDateTime( Hours*(MinsPerHour*SecsPerMin) +
Minutes*(SecsPerMin) +
Seconds
) - UnixEpoch;
if (Hours <= -24) or (Hours >= 24) then begin // Can the "Hours" be negative? Not compatible with FormatDateTime?
// A day or longer
case FDateMode of
dm1899: Result := Result + DATEMODE_1899_BASE;
dm1900: Result := Result + DATEMODE_1900_BASE;
dm1904: Result := Result + DATEMODE_1904_BASE;
end;
end;
end;
end;
end;
function TsSpreadOpenDocReader.FindCellStyleByName(AStyleName: String): Integer;
begin
for Result:=0 to FCellStyleList.Count-1 do begin
@ -773,112 +848,17 @@ begin
ApplyStyleToCell(ARow, ACol, stylename);
end;
procedure TsSpreadOpenDocReader.ReadDate(ARow: Word; ACol : Word; ACellNode : TDOMNode);
procedure TsSpreadOpenDocReader.ReadDateTime(ARow: Word; ACol : Word;
ACellNode : TDOMNode);
var
dt: TDateTime;
Value: String;
Fmt : TFormatSettings;
FoundPos : integer;
Hours, Minutes, Seconds: integer;
HoursPos, MinutesPos, SecondsPos: integer;
styleName: String;
begin
// Format expects ISO 8601 type date string or
// time string
fmt := DefaultFormatSettings;
fmt.ShortDateFormat:='yyyy-mm-dd';
fmt.DateSeparator:='-';
fmt.LongTimeFormat:='hh:nn:ss';
fmt.TimeSeparator:=':';
Value:=GetAttrValue(ACellNode,'office:date-value');
if Value<>'' then
begin (* // confuses fpc!
{$IFDEF FPSPREADDEBUG}
end;
writeln('Row (1based): ',ARow+1,'office:date-value: '+Value);
{$ENDIF} *)
dt := ExtractDateTimeFromNode(ACellNode);
FWorkSheet.WriteDateTime(ARow, ACol, dt);
// Date or date/time string
Value:=StringReplace(Value,'T',' ',[rfIgnoreCase,rfReplaceAll]);
// Strip milliseconds?
FoundPos:=Pos('.',Value);
if (FoundPos>1) then
begin
Value:=Copy(Value,1,FoundPos-1);
end;
dt:=StrToDateTime(Value,Fmt);
FWorkSheet.WriteDateTime(Arow,ACol,dt);
end
else
begin
// Try time only, e.g. PT23H59M59S
// 12345678901
Value:=GetAttrValue(ACellNode,'office:time-value');
{$IFDEF FPSPREADDEBUG}
writeln('Row (1based): ',ARow+1,'office:time-value: '+Value);
{$ENDIF}
if (Value<>'') and (Pos('PT',Value)=1) then
begin
// Get hours
HoursPos:=Pos('H',Value);
if (HoursPos>0) then
Hours:=StrToInt(Copy(Value,3,HoursPos-3))
else
Hours:=0;
// Get minutes
MinutesPos:=Pos('M',Value);
if (MinutesPos>0) and (MinutesPos>HoursPos) then
Minutes:=StrToInt(Copy(Value,HoursPos+1,MinutesPos-HoursPos-1))
else
Minutes:=0;
// Get seconds
SecondsPos:=Pos('S',Value);
if (SecondsPos>0) and (SecondsPos>MinutesPos) then
Seconds:=StrToInt(Copy(Value,MinutesPos+1,SecondsPos-MinutesPos-1))
else
Seconds:=0;
// Times smaller than a day can be taken as is
// Times larger than a day depend on the file's date mode.
// Convert to date/time via Unix timestamp so avoiding limits for number of
// hours etc in EncodeDateTime. Perhaps there's a faster way of doing this?
if (Hours>-24) and (Hours<24) then
begin
dt:=UnixToDateTime(
Hours*(MinsPerHour*SecsPerMin)+
Minutes*(SecsPerMin)+
Seconds
)-UnixEpoch;
end
else
begin
// A day or longer
case FDateMode of
dm1899:
dt:=DATEMODE_1899_BASE+UnixToDateTime(
Hours*(MinsPerHour*SecsPerMin)+
Minutes*(SecsPerMin)+
Seconds
)-UnixEpoch;
dm1900:
dt:=DATEMODE_1900_BASE+UnixToDateTime(
Hours*(MinsPerHour*SecsPerMin)+
Minutes*(SecsPerMin)+
Seconds
)-UnixEpoch;
dm1904:
dt:=DATEMODE_1904_BASE+UnixToDateTime(
Hours*(MinsPerHour*SecsPerMin)+
Minutes*(SecsPerMin)+
Seconds
)-UnixEpoch;
end;
end;
FWorkSheet.WriteDateTime(Arow,ACol,dt);
end;
end;
styleName := GetAttrValue(ACellNode, 'table:style-name');
ApplyStyleToCell(ARow, ACol, stylename);
end;
procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
@ -1077,7 +1057,7 @@ begin
else if (paramValueType = 'float') or (paramValueType = 'percentage') then
ReadNumber(row, col, cellNode)
else if (paramValueType = 'date') or (paramValueType = 'time') then
ReadDate(row, col, cellNode)
ReadDateTime(row, col, cellNode)
else if (paramValueType = '') and (tableStyleName <> '') then
ReadBlank(row, col, cellNode)
else if ParamFormula <> '' then