From ba5e4da9be0e29d3eaa8b47b27d779f976eaec59 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 9 Aug 2018 22:00:21 +0000 Subject: [PATCH] fpspreadsheet: Fix bug in formula parser when scanning of ods cell addresses (incorrect extraction of row index). git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6577 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../source/common/fpsexprparser.pas | 45 ++++--- .../source/common/fpsopendocument.pas | 126 +++++++++--------- 2 files changed, 90 insertions(+), 81 deletions(-) diff --git a/components/fpspreadsheet/source/common/fpsexprparser.pas b/components/fpspreadsheet/source/common/fpsexprparser.pas index 22ef3a7b9..2480ddf71 100644 --- a/components/fpspreadsheet/source/common/fpsexprparser.pas +++ b/components/fpspreadsheet/source/common/fpsexprparser.pas @@ -953,7 +953,7 @@ end; It has the structure [sheet1.C1R1:sheet2.C2R2] } function TsExpressionScanner.DoCellRangeODS: TsTokenType; type - TScannerStateODS = (ssSheet1, ssCol1, ssRow1, ssSheet2, ssCol2, ssRow2); + TScannerStateODS = (ssInSheet1, ssInCol1, ssInRow1, ssInSheet2, ssInCol2, ssInRow2); var C: Char; prevC: Char; @@ -968,7 +968,7 @@ begin FCellRange.Col2 := Cardinal(-1); FFlags := rfAllRel; - state := ssSheet1; + state := ssInSheet1; FToken := ''; C := NextPos; prevC := #0; @@ -976,33 +976,33 @@ begin case C of cNULL: ScanError(rsUnexpectedEndOfExpression); '.': begin - if (state = ssSheet1) then + if (state = ssInSheet1) then begin FSheet1 := FToken; - state := ssCol1; + state := ssInCol1; end else - if (state = ssSheet2) then + if (state = ssInSheet2) then begin FSheet2 := FToken; - state := ssCol2; + state := ssInCol2; end else ScanError(rsIllegalODSCellRange); FToken := ''; val := 0; end; - ':': if (state = ssRow1) then + ':': if (state = ssInRow1) then begin FCellRange.Row1 := val-1; - state := ssSheet2; + state := ssInSheet2; FToken := ''; end else ScanError(rsIllegalODSCellRange); '$': case state of - ssCol1: if prevC = '.' then Exclude(FFlags, rfRelCol) else Exclude(FFlags, rfRelRow); - ssCol2: if prevC = '.' then Exclude(FFlags, rfRelCol2) else Exclude(FFlags, rfRelRow2); + ssInCol1: if prevC = '.' then Exclude(FFlags, rfRelCol) else Exclude(FFlags, rfRelRow); + ssInCol2: if prevC = '.' then Exclude(FFlags, rfRelCol2) else Exclude(FFlags, rfRelRow2); end; else - if (state in [ssSheet1, ssSheet2]) then + if (state in [ssInSheet1, ssInSheet2]) then FToken := FToken + C else case C of @@ -1011,16 +1011,21 @@ begin 'a'..'z': val := val*10 + ord(C) - ord('a'); '0'..'9': - if state = ssCol1 then begin + if state = ssInCol1 then begin FCellRange.Col1 := val; - val := ord(C) - ord('0'); - state := ssRow1; + val := (ord(C) - ord('0')); + state := ssInRow1; end else - if state = ssCol2 then begin + if state = ssInRow1 then + val := val*10 + (ord(C) - ord('0')) + else + if state = ssInCol2 then begin FCellRange.Col2 := val; - val := ord(C) - ord('0'); - state := ssRow2; - end; + val := (ord(C) - ord('0')); + state := ssInRow2; + end else + if state = ssInRow2 then + val := val*10 + (ord(C) - ord('0')); end; end; prevC := C; @@ -1029,9 +1034,9 @@ begin if C <> ']' then ScanError(Format(rsRightSquareBracketExpected, [FPos, C])); case state of - ssRow1: + ssInRow1: if val > 0 then FCellRange.Row1 := val - 1 else ScanError(rsIllegalODSCellRange); - ssRow2: + ssInRow2: if val > 0 then FCellRange.Row2 := val - 1 else ScanError(rsIllegalODSCellRange); end; if FCellRange.Col2 = Cardinal(-1) then Exclude(FFlags, rfRelCol2); diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas index eb9311bb4..be05f3a6d 100644 --- a/components/fpspreadsheet/source/common/fpsopendocument.pas +++ b/components/fpspreadsheet/source/common/fpsopendocument.pas @@ -2463,74 +2463,78 @@ begin hasFormula := false; // Read formula results - if hasFormula then TsWorkbook(FWorkbook).LockFormulas; - valueType := GetAttrValue(ACellNode, 'office:value-type'); - valueStr := GetAttrValue(ACellNode, 'office:value'); - calcExtValueType := GetAttrValue(ACellNode, 'calcext:value-type'); - // ODS wants a 0 in the NumberValue field in case of an error. If there is - // no error, this value will be corrected below. - cell^.NumberValue := 0.0; - // (a) number value - if (valueType = 'float') then - begin - if UpperCase(valueStr) = '1.#INF' then - TsWorksheet(FWorksheet).WriteNumber(cell, 1.0/0.0) - else + // Prevent formulas from being erased when formula results are written to cells + TsWorkbook(FWorkbook).LockFormulas; + try + valueType := GetAttrValue(ACellNode, 'office:value-type'); + valueStr := GetAttrValue(ACellNode, 'office:value'); + calcExtValueType := GetAttrValue(ACellNode, 'calcext:value-type'); + // ODS wants a 0 in the NumberValue field in case of an error. If there is + // no error, this value will be corrected below. + cell^.NumberValue := 0.0; + // (a) number value + if (valueType = 'float') then begin - floatValue := StrToFloat(valueStr, FPointSeparatorSettings); - TsWorksheet(FWorksheet).WriteNumber(cell, floatValue); - end; - if IsDateTimeFormat(fmt^.NumberFormat) then - begin - cell^.ContentType := cctDateTime; - // No datemode correction for intervals and for time-only values - if (fmt^.NumberFormat = nfTimeInterval) or (cell^.NumberValue < 1) then - cell^.DateTimeValue := cell^.NumberValue + if UpperCase(valueStr) = '1.#INF' then + TsWorksheet(FWorksheet).WriteNumber(cell, 1.0/0.0) else - case FDateMode of - dmODS1899: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1899_BASE; - dmODS1900: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1900_BASE; - dmODS1904: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1904_BASE; - end; - end; - end else - // (b) Date/time value - if (valueType = 'date') or (valueType = 'time') then - begin - floatValue := ExtractDateTimeFromNode(ACellNode, fmt^.NumberFormat, fmt^.NumberFormatStr); - TsWorksheet(FWorkSheet).WriteDateTime(cell, floatValue); - end else - // (c) text - if (valueType = 'string') and (calcextValueType <> 'error') then - begin - node := ACellNode.FindNode('text:p'); - if (node <> nil) and (node.FirstChild <> nil) then + begin + floatValue := StrToFloat(valueStr, FPointSeparatorSettings); + TsWorksheet(FWorksheet).WriteNumber(cell, floatValue); + end; + if IsDateTimeFormat(fmt^.NumberFormat) then + begin + cell^.ContentType := cctDateTime; + // No datemode correction for intervals and for time-only values + if (fmt^.NumberFormat = nfTimeInterval) or (cell^.NumberValue < 1) then + cell^.DateTimeValue := cell^.NumberValue + else + case FDateMode of + dmODS1899: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1899_BASE; + dmODS1900: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1900_BASE; + dmODS1904: cell^.DateTimeValue := cell^.NumberValue + DATEMODE_1904_BASE; + end; + end; + end else + // (b) Date/time value + if (valueType = 'date') or (valueType = 'time') then begin - valueStr := node.FirstChild.Nodevalue; + floatValue := ExtractDateTimeFromNode(ACellNode, fmt^.NumberFormat, fmt^.NumberFormatStr); + TsWorksheet(FWorkSheet).WriteDateTime(cell, floatValue); + end else + // (c) text + if (valueType = 'string') and (calcextValueType <> 'error') then + begin + node := ACellNode.FindNode('text:p'); + if (node <> nil) and (node.FirstChild <> nil) then + begin + valueStr := node.FirstChild.Nodevalue; + TsWorksheet(FWorksheet).WriteText(cell, valueStr); + end; + end else + // (d) boolean + if (valuetype = 'boolean') then + begin + boolValue := ExtractBoolFromNode(ACellNode); + TsWorksheet(FWorksheet).WriteBoolValue(cell, boolValue); + end else + if (calcextValuetype = 'error') then + begin + if ExtractErrorFromNode(ACellNode, errorValue) then + TsWorksheet(FWorksheet).WriteErrorValue(cell, errorValue) else + TsWorksheet(FWorksheet).WriteText(cell, 'ERROR'); + end else + // (e) Text + if (valueStr <> '') then TsWorksheet(FWorksheet).WriteText(cell, valueStr); - end; - end else - // (d) boolean - if (valuetype = 'boolean') then - begin - boolValue := ExtractBoolFromNode(ACellNode); - TsWorksheet(FWorksheet).WriteBoolValue(cell, boolValue); - end else - if (calcextValuetype = 'error') then - begin - if ExtractErrorFromNode(ACellNode, errorValue) then - TsWorksheet(FWorksheet).WriteErrorValue(cell, errorValue) else - TsWorksheet(FWorksheet).WriteText(cell, 'ERROR'); - end else - // (e) Text - if (valueStr <> '') then - TsWorksheet(FWorksheet).WriteText(cell, valueStr); - if hasFormula then TsWorkbook(FWorkbook).UnlockFormulas; + if FIsVirtualMode then + TsWorkbook(Workbook).OnReadCellData(Workbook, ARow, ACol, cell); - if FIsVirtualMode then - TsWorkbook(Workbook).OnReadCellData(Workbook, ARow, ACol, cell); + finally + TsWorkbook(FWorkbook).UnlockFormulas; + end; end; procedure TsSpreadOpenDocReader.ReadFromStream(AStream: TStream;