fpspreadsheet: Consider 1900/1904 date mode for reading/writing of xlsx files.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3383 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-26 21:18:49 +00:00
parent adb1e5c13e
commit 77d6fd704d

View File

@ -41,7 +41,7 @@ uses
{$ENDIF}
laz2_xmlread, laz2_DOM,
AVL_Tree,
fpspreadsheet, fpsutils, fpsxmlcommon;
fpspreadsheet, fpsutils, fpsxmlcommon, xlscommon;
type
@ -58,18 +58,21 @@ type
TsSpreadOOXMLReader = class(TsSpreadXMLReader)
private
FDateMode: TDateMode;
FPointSeparatorSettings: TFormatSettings;
FSharedStrings: TStringList;
FXfList: TFPList;
FFillList: TFPList;
FBorderList: TFPList;
procedure ReadCell(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadCellXfs(ANode: TDOMNode);
procedure ReadDateMode(ANode: TDOMNode);
procedure ReadFont(ANode: TDOMNode);
procedure ReadFonts(ANode: TDOMNode);
procedure ReadNumFormats(ANode: TDOMNode);
procedure ReadSharedStrings(ANode: TDOMNode);
procedure ReadSheetList(ANode: TDOMNode; AList: TStrings);
procedure ReadWorksheet(ANode: TDOMNode; ASheet: TsWorksheet);
procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet);
protected
procedure ApplyCellFormatting(ACell: PCell; XfIndex: Integer);
procedure CreateNumFormatList; override;
@ -82,8 +85,8 @@ type
{ TsSpreadOOXMLWriter }
TsSpreadOOXMLWriter = class(TsCustomSpreadWriter)
private
protected
FDateMode: TDateMode;
FPointSeparatorSettings: TFormatSettings;
FSharedStringsCount: Integer;
FFillList: array of PCell;
@ -141,7 +144,7 @@ type
implementation
uses
variants, fileutil, fpsStreams, fpsNumFormatParser, xlscommon;
variants, fileutil, fpsStreams, fpsNumFormatParser;
const
{ OOXML general XML constants }
@ -271,6 +274,7 @@ end;
constructor TsSpreadOOXMLReader.Create(AWorkbook: TsWorkbook);
begin
inherited Create(AWorkbook);
FDateMode := dm1900;
// Set up the default palette in order to have the default color names correct.
Workbook.UseDefaultPalette;
@ -370,6 +374,103 @@ begin
FNumFormatList := TsOOXMLNumFormatList.Create(Workbook);
end;
procedure TsSpreadOOXMLReader.ReadCell(ANode: TDOMNode; AWorksheet: TsWorksheet);
var
s: String;
rowIndex, colIndex: Cardinal;
cell: PCell;
datanode: TDOMNode;
dataStr: String;
formulaStr: String;
sstIndex: Integer;
number: Double;
begin
if ANode = nil then
exit;
// get row and column address
s := GetAttrValue(ANode, 'r'); // cell address, like 'A1'
ParseCellString(s, rowIndex, colIndex);
// create cell
if FIsVirtualMode then begin
InitCell(rowIndex, colIndex, FVirtualCell);
cell := @FVirtualCell;
end else
cell := AWorksheet.GetCell(rowIndex, colIndex);
// get style index
s := GetAttrValue(ANode, 's');
ApplyCellFormatting(cell, StrToInt(s));
// get data
datanode := ANode.FirstChild;
dataStr := '';
formulaStr := '';
while Assigned(datanode) do begin
if datanode.NodeName = 'v' then
dataStr := GetNodeValue(datanode)
else
if datanode.NodeName = 'f' then
formulaStr := GetNodeValue(datanode);
datanode := datanode.NextSibling;
end;
// formula to cell
if formulaStr <> '' then
cell^.FormulaValue.FormulaStr := '=' + formulaStr;
// get data type
s := GetAttrValue(ANode, 't'); // "t" = data type
if (s = '') and (dataStr = '') then
AWorksheet.WriteBlank(cell)
else
if (s = '') or (s = 'n') then begin
// Number or date/time, depending on format
number := StrToFloat(dataStr, FPointSeparatorSettings);
if IsDateTimeFormat(cell^.NumberFormatStr) then begin
number := ConvertExcelDateTimeToDateTime(number, FDateMode);
AWorksheet.WriteDateTime(cell, number, cell^.NumberFormatStr)
end
else
AWorksheet.WriteNumber(cell, number);
end
else
if s = 's' then begin
// String from shared strings table
sstIndex := StrToInt(dataStr);
AWorksheet.WriteUTF8Text(cell, FSharedStrings[sstIndex]);
end else
if s = 'str' then
// literal string
AWorksheet.WriteUTF8Text(cell, datastr)
else
if s = 'b' then
// boolean
AWorksheet.WriteBoolValue(cell, dataStr='1')
else
if s = 'e' then begin
// error value
if dataStr = '#NULL!' then
AWorksheet.WriteErrorValue(cell, errEmptyIntersection)
else if dataStr = '#DIV/0!' then
AWorksheet.WriteErrorValue(cell, errDivideByZero)
else if dataStr = '#VALUE!' then
AWorksheet.WriteErrorValue(cell, errWrongType)
else if dataStr = '#REF!' then
AWorksheet.WriteErrorValue(cell, errIllegalRef)
else if dataStr = '#NAME?' then
AWorksheet.WriteErrorValue(cell, errWrongName)
else if dataStr = '#NUM!' then
AWorksheet.WriteErrorValue(cell, errOverflow)
else if dataStr = '#N/A' then
AWorksheet.WriteErrorValue(cell, errArgError)
else
raise Exception.Create('unknown error type');
end else
raise Exception.Create('Unknown data type');
end;
procedure TsSpreadOOXMLReader.ReadCellXfs(ANode: TDOMNode);
var
node: TDOMNode;
@ -405,6 +506,16 @@ begin
end;
end;
procedure TsSpreadOOXMLReader.ReadDateMode(ANode: TDOMNode);
var
s: String;
begin
if Assigned(ANode) then begin
s := GetAttrValue(ANode, 'date1904');
if s = '1' then FDateMode := dm1904
end;
end;
procedure TsSpreadOOXMLReader.ReadFont(ANode: TDOMNode);
var
node: TDOMNode;
@ -528,106 +639,18 @@ begin
end;
end;
procedure TsSpreadOOXMLReader.ReadWorksheet(ANode: TDOMNode; ASheet: TsWorksheet);
procedure TsSpreadOOXMLReader.ReadWorksheet(ANode: TDOMNode; AWorksheet: TsWorksheet);
var
rownode: TDOMNode;
cellnode: TDOMNode;
datanode: TDOMNode;
s: String;
rowIndex, colIndex: Cardinal;
dataStr: String;
formulaStr: String;
sstIndex: Integer;
cell: PCell;
number: Double;
begin
rownode := ANode.FirstChild;
while Assigned(rownode) do begin
if rownode.NodeName = 'row' then begin
cellnode := rownode.FirstChild;
while Assigned(cellnode) do begin
if cellnode.NodeName = 'c' then begin
// get row and column address
s := GetAttrValue(cellnode, 'r'); // cell address, like 'A1'
ParseCellString(s, rowIndex, colIndex);
// create cell
if FIsVirtualMode then begin
InitCell(rowIndex, colIndex, FVirtualCell);
cell := @FVirtualCell;
end else
cell := ASheet.GetCell(rowIndex, colIndex);
// get style index
s := GetAttrValue(cellnode, 's');
ApplyCellFormatting(cell, StrToInt(s));
// get data
datanode := cellnode.FirstChild;
dataStr := '';
formulaStr := '';
while Assigned(datanode) do begin
if datanode.NodeName = 'v' then
dataStr := GetNodeValue(datanode)
else
if datanode.NodeName = 'f' then
formulaStr := GetNodeValue(datanode);
datanode := datanode.NextSibling;
end;
// formula to cell
if formulaStr <> '' then
cell^.FormulaValue.FormulaStr := '=' + formulaStr;
// get data type
s := GetAttrValue(cellnode, 't'); // "t" = data type
if (s = '') and (dataStr = '') then
ASheet.WriteBlank(cell)
else
if (s = '') or (s = 'n') then begin
// Number or date/time, depending on format
number := StrToFloat(dataStr, FPointSeparatorSettings);
if IsDateTimeFormat(cell^.NumberFormatStr) then
ASheet.WriteDateTime(cell, number, cell^.NumberFormatStr)
else
ASheet.WriteNumber(cell, number);
end
else
if s = 's' then begin
// String from shared strings table
sstIndex := StrToInt(dataStr);
ASheet.WriteUTF8Text(cell, FSharedStrings[sstIndex]);
end else
if s = 'str' then
// literal string
ASheet.WriteUTF8Text(cell, datastr)
else
if s = 'b' then
// boolean
ASheet.WriteBoolValue(cell, dataStr='1')
else
if s = 'e' then begin
// error value
if dataStr = '#NULL!' then
ASheet.WriteErrorValue(cell, errEmptyIntersection)
else if dataStr = '#DIV/0!' then
ASheet.WriteErrorValue(cell, errDivideByZero)
else if dataStr = '#VALUE!' then
ASheet.WriteErrorValue(cell, errWrongType)
else if dataStr = '#REF!' then
ASheet.WriteErrorValue(cell, errIllegalRef)
else if dataStr = '#NAME?' then
ASheet.WriteErrorValue(cell, errWrongName)
else if dataStr = '#NUM!' then
ASheet.WriteErrorValue(cell, errOverflow)
else if dataStr = '#N/A' then
ASheet.WriteErrorValue(cell, errArgError)
else
raise Exception.Create('unknown error type');
end else
raise Exception.Create('Unknown data type');
end;
if cellnode.NodeName = 'c' then
ReadCell(cellnode, AWorksheet);
cellnode := cellnode.NextSibling;
end;
end;
@ -645,10 +668,6 @@ var
i: Integer;
fn: String;
BodyNode, SpreadSheetNode, TableNode: TDOMNode;
StylesNode: TDOMNode;
OfficeSettingsNode: TDOMNode;
s: String;
node: TDOMNode;
@ -694,7 +713,7 @@ begin
// process the workbook.xml file
ReadXMLFile(Doc, FilePath + OOXML_PATH_XL_WORKBOOK);
DeleteFile(FilePath + OOXML_PATH_XL_WORKBOOK);
ReadDateMode(Doc.DocumentElement.FindNode('workbookPr'));
ReadSheetList(Doc.DocumentElement.FindNode('sheets'), SheetList);
FreeAndNil(Doc);
@ -1550,6 +1569,11 @@ end;
constructor TsSpreadOOXMLWriter.Create(AWorkbook: TsWorkbook);
begin
inherited Create(AWorkbook);
// Initial base date in case it won't be set otherwise.
// Use 1900 to get a bit more range between 1900..1904.
FDateMode := dm1900;
// Special version of FormatSettings using a point decimal separator for sure.
FPointSeparatorSettings := DefaultFormatSettings;
FPointSeparatorSettings.DecimalSeparator := '.';
@ -1801,11 +1825,13 @@ var
CellPosText: String;
CellValueText: String;
lStyleIndex: Integer;
lDateTime: TDateTime;
begin
Unused(AStream, ACell);
CellPosText := TsWorksheet.CellPosToText(ARow, ACol);
CellValueText := Format('%g', [AValue], FPointSeparatorSettings);
lStyleIndex := GetStyleIndex(ACell);
lDateTime := ConvertDateTimeToExcelDateTime(AValue, FDateMode);
CellValueText := Format('%g', [lDateTime], FPointSeparatorSettings);
AppendToStream(AStream, Format(
'<c r="%s" s="%d" t="n"><v>%s</v></c>', [CellPosText, lStyleIndex, CellValueText]));
end;