You've already forked lazarus-ccr
fpspreadsheet: More improvements for reading numbers in csv files and automatic format detection.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3672 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@@ -111,12 +111,10 @@
|
|||||||
<ComponentName Value="CSVParamsForm"/>
|
<ComponentName Value="CSVParamsForm"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="sCSVParamsForm"/>
|
|
||||||
</Unit2>
|
</Unit2>
|
||||||
<Unit3>
|
<Unit3>
|
||||||
<Filename Value="sctrls.pas"/>
|
<Filename Value="sctrls.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="sCtrls"/>
|
|
||||||
</Unit3>
|
</Unit3>
|
||||||
<Unit4>
|
<Unit4>
|
||||||
<Filename Value="sformatsettingsform.pas"/>
|
<Filename Value="sformatsettingsform.pas"/>
|
||||||
@@ -124,7 +122,6 @@
|
|||||||
<ComponentName Value="FormatSettingsForm"/>
|
<ComponentName Value="FormatSettingsForm"/>
|
||||||
<HasResources Value="True"/>
|
<HasResources Value="True"/>
|
||||||
<ResourceBaseClass Value="Form"/>
|
<ResourceBaseClass Value="Form"/>
|
||||||
<UnitName Value="sFormatsettingsForm"/>
|
|
||||||
</Unit4>
|
</Unit4>
|
||||||
</Units>
|
</Units>
|
||||||
</ProjectOptions>
|
</ProjectOptions>
|
||||||
|
@@ -14,8 +14,8 @@ type
|
|||||||
FWorksheetName: String;
|
FWorksheetName: String;
|
||||||
function IsBool(AText: String; out AValue: Boolean): Boolean;
|
function IsBool(AText: String; out AValue: Boolean): Boolean;
|
||||||
function IsDateTime(AText: String; out ADateTime: TDateTime): Boolean;
|
function IsDateTime(AText: String; out ADateTime: TDateTime): Boolean;
|
||||||
function IsNumber(AText: String; out ANumber: Double;
|
function IsNumber(AText: String; out ANumber: Double; out ANumFormat: TsNumberFormat;
|
||||||
out ACurrencySymbol, AWarning: String): Boolean;
|
out ADecimals: Integer; out ACurrencySymbol, AWarning: String): Boolean;
|
||||||
function IsQuotedText(var AText: String): Boolean;
|
function IsQuotedText(var AText: String): Boolean;
|
||||||
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
|
||||||
protected
|
protected
|
||||||
@@ -207,10 +207,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function TsCSVReader.IsNumber(AText: String; out ANumber: Double;
|
function TsCSVReader.IsNumber(AText: String; out ANumber: Double;
|
||||||
|
out ANumFormat: TsNumberFormat; out ADecimals: Integer;
|
||||||
out ACurrencySymbol, AWarning: String): Boolean;
|
out ACurrencySymbol, AWarning: String): Boolean;
|
||||||
var
|
var
|
||||||
p: Integer;
|
p: Integer;
|
||||||
|
decsep, thsep: Char;
|
||||||
begin
|
begin
|
||||||
|
Result := false;
|
||||||
AWarning := '';
|
AWarning := '';
|
||||||
|
|
||||||
// To detect whether the text is a currency value we look for the currency
|
// To detect whether the text is a currency value we look for the currency
|
||||||
@@ -223,11 +226,8 @@ begin
|
|||||||
if p > 0 then begin
|
if p > 0 then begin
|
||||||
Delete(AText, p, Length(ACurrencySymbol));
|
Delete(AText, p, Length(ACurrencySymbol));
|
||||||
AText := Trim(AText);
|
AText := Trim(AText);
|
||||||
if AText = '' then begin
|
if AText = '' then
|
||||||
Result := false;
|
|
||||||
ACurrencySymbol := '';
|
|
||||||
exit;
|
exit;
|
||||||
end;
|
|
||||||
// Negative financial values are often enclosed by parenthesis
|
// Negative financial values are often enclosed by parenthesis
|
||||||
if ((AText[1] = '(') and (AText[Length(AText)] = ')')) then
|
if ((AText[1] = '(') and (AText[Length(AText)] = ')')) then
|
||||||
AText := '-' + Trim(Copy(AText, 2, Length(AText)-2));
|
AText := '-' + Trim(Copy(AText, 2, Length(AText)-2));
|
||||||
@@ -235,11 +235,57 @@ begin
|
|||||||
ACurrencySymbol := '';
|
ACurrencySymbol := '';
|
||||||
|
|
||||||
if CSVParams.AutoDetectNumberFormat then
|
if CSVParams.AutoDetectNumberFormat then
|
||||||
Result := TryStrToFloatAuto(AText, ANumber, AWarning)
|
Result := TryStrToFloatAuto(AText, ANumber, decsep, thsep, AWarning)
|
||||||
else
|
else begin
|
||||||
Result := TryStrToFloat(AText, ANumber, CSVParams.FormatSettings);
|
Result := TryStrToFloat(AText, ANumber, CSVParams.FormatSettings);
|
||||||
|
if Result then
|
||||||
|
begin
|
||||||
|
if pos(CSVParams.FormatSettings.DecimalSeparator, AText) = 0
|
||||||
|
then decsep := #0
|
||||||
|
else decsep := CSVParams.FormatSettings.DecimalSeparator;
|
||||||
|
if pos(CSVParams.FormatSettings.ThousandSeparator, AText) = 0
|
||||||
|
then thsep := #0
|
||||||
|
else thsep := CSVParams.FormatSettings.ThousandSeparator;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
if not Result then ACurrencySymbol := '';
|
// Try to determine the number format
|
||||||
|
if Result then
|
||||||
|
begin
|
||||||
|
if thsep <> #0 then
|
||||||
|
ANumFormat := nfFixedTh
|
||||||
|
else
|
||||||
|
ANumFormat := nfGeneral;
|
||||||
|
// count number of decimal places and try to catch special formats
|
||||||
|
ADecimals := 0;
|
||||||
|
if decsep <> #0 then
|
||||||
|
begin
|
||||||
|
// Go to the decimal separator and search towards the end of the string
|
||||||
|
p := pos(decsep, AText) + 1;
|
||||||
|
while (p <= Length(AText)) do begin
|
||||||
|
// exponential format
|
||||||
|
if AText[p] in ['+', '-', 'E', 'e'] then
|
||||||
|
begin
|
||||||
|
ANumFormat := nfExp;
|
||||||
|
break;
|
||||||
|
end else
|
||||||
|
// percent format
|
||||||
|
if AText[p] = '%' then
|
||||||
|
begin
|
||||||
|
ANumFormat := nfPercentage;
|
||||||
|
break;
|
||||||
|
end else
|
||||||
|
begin
|
||||||
|
inc(p);
|
||||||
|
inc(ADecimals);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if (ADecimals > 0) and (ADecimals < 9) and (ANumFormat = nfGeneral) then
|
||||||
|
// "no formatting" assumed if there are "many" decimals
|
||||||
|
ANumFormat := nfFixed;
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
ACurrencySymbol := '';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TsCSVReader.IsQuotedText(var AText: String): Boolean;
|
function TsCSVReader.IsQuotedText(var AText: String): Boolean;
|
||||||
@@ -268,6 +314,7 @@ var
|
|||||||
currSym: string;
|
currSym: string;
|
||||||
warning: String;
|
warning: String;
|
||||||
nf: TsNumberFormat;
|
nf: TsNumberFormat;
|
||||||
|
decs: Integer;
|
||||||
begin
|
begin
|
||||||
// Empty strings are blank cells -- nothing to do
|
// Empty strings are blank cells -- nothing to do
|
||||||
if AText = '' then
|
if AText = '' then
|
||||||
@@ -282,7 +329,10 @@ begin
|
|||||||
|
|
||||||
// Remove quotes
|
// Remove quotes
|
||||||
if (AText[1] = CSVParams.QuoteChar) and (AText[Length(AText)] = CSVParams.QuoteChar) then
|
if (AText[1] = CSVParams.QuoteChar) and (AText[Length(AText)] = CSVParams.QuoteChar) then
|
||||||
Delete(AText, 2, Length(AText)-2);
|
begin
|
||||||
|
Delete(AText, Length(AText), 1);
|
||||||
|
Delete(AText, 1, 1);
|
||||||
|
end;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Quoted text is a TEXT cell
|
// Quoted text is a TEXT cell
|
||||||
@@ -294,12 +344,12 @@ begin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for a NUMBER or CURRENCY cell
|
// Check for a NUMBER or CURRENCY cell
|
||||||
if IsNumber(AText, dblValue, currSym, warning) then
|
if IsNumber(AText, dblValue, nf, decs, currSym, warning) then
|
||||||
begin
|
begin
|
||||||
if currSym <> '' then
|
if currSym <> '' then
|
||||||
FWorksheet.WriteCurrency(ARow, ACol, dblValue, nfCurrency, 2, currSym)
|
FWorksheet.WriteCurrency(ARow, ACol, dblValue, nfCurrency, decs, currSym)
|
||||||
else
|
else
|
||||||
FWorksheet.WriteNumber(ARow, ACol, dblValue);
|
FWorksheet.WriteNumber(ARow, ACol, dblValue, nf, decs);
|
||||||
if warning <> '' then
|
if warning <> '' then
|
||||||
FWorkbook.AddErrorMsg('Cell %s: %s', [GetCellString(ARow, ACol), warning]);
|
FWorkbook.AddErrorMsg('Cell %s: %s', [GetCellString(ARow, ACol), warning]);
|
||||||
exit;
|
exit;
|
||||||
|
@@ -130,7 +130,7 @@ function FormatDateTime(const FormatStr: string; DateTime: TDateTime;
|
|||||||
const FormatSettings: TFormatSettings; Options : TFormatDateTimeOptions = []): string;
|
const FormatSettings: TFormatSettings; Options : TFormatDateTimeOptions = []): string;
|
||||||
|
|
||||||
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
||||||
out AWarning: String): Boolean;
|
out ADecimalSeparator, AThousandSeparator: Char; out AWarning: String): Boolean;
|
||||||
|
|
||||||
function TwipsToPts(AValue: Integer): Single;
|
function TwipsToPts(AValue: Integer): Single;
|
||||||
function PtsToTwips(AValue: Single): Integer;
|
function PtsToTwips(AValue: Single): Integer;
|
||||||
@@ -1481,17 +1481,21 @@ end;
|
|||||||
Is needed for reading CSV files.
|
Is needed for reading CSV files.
|
||||||
-------------------------------------------------------------------------------}
|
-------------------------------------------------------------------------------}
|
||||||
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
function TryStrToFloatAuto(AText: String; out ANumber: Double;
|
||||||
out AWarning: String): Boolean;
|
out ADecimalSeparator, AThousandSeparator: Char; out AWarning: String): Boolean;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
testSep: Char;
|
testSep: Char;
|
||||||
testSepPos: Integer;
|
testSepPos: Integer;
|
||||||
|
lastDigitPos: Integer;
|
||||||
isPercent: Boolean;
|
isPercent: Boolean;
|
||||||
|
isExp: Boolean;
|
||||||
fs: TFormatSettings;
|
fs: TFormatSettings;
|
||||||
done: Boolean;
|
done: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := false;
|
Result := false;
|
||||||
AWarning := '';
|
AWarning := '';
|
||||||
|
ADecimalSeparator := #0;
|
||||||
|
AThousandSeparator := #0;
|
||||||
if AText = '' then
|
if AText = '' then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
@@ -1506,65 +1510,89 @@ begin
|
|||||||
// but no decimal separator misinterprets the thousand separator as a
|
// but no decimal separator misinterprets the thousand separator as a
|
||||||
// decimal separator.
|
// decimal separator.
|
||||||
|
|
||||||
done := false; // Indicates that both decimal and thousand separators are found
|
done := false; // Indicates that both decimal and thousand separators are found
|
||||||
testSep := #0; // Separator candidate to be tested
|
testSep := #0; // Separator candidate to be tested
|
||||||
testSepPos := 0; // Position of this separator chandidate in the string
|
testSepPos := 0; // Position of this separator candidate in the string
|
||||||
|
lastDigitPos := 0; // Position of the last numerical digit
|
||||||
|
isExp := false; // Flag for exponential format
|
||||||
|
isPercent := false; // Flag for percentage format
|
||||||
|
|
||||||
i := Length(AText); // Start at end...
|
i := Length(AText); // Start at end...
|
||||||
while i >= 1 do // ...and search towards start
|
while i >= 1 do // ...and search towards start
|
||||||
begin
|
begin
|
||||||
if AText[i] in ['.', ','] then
|
case AText[i] of
|
||||||
begin
|
'0'..'9':
|
||||||
if testSep = #0 then begin
|
if (lastDigitPos = 0) and (AText[i] in ['0'..'9']) then
|
||||||
testSep := AText[i];
|
lastDigitPos := i;
|
||||||
testSepPos := i;
|
|
||||||
end;
|
'e', 'E':
|
||||||
// This is the right-most separator candidate in the text
|
isExp := true;
|
||||||
// It can be a decimal or a thousand separator.
|
|
||||||
dec(i);
|
'%':
|
||||||
while i >= 1 do
|
isPercent := true;
|
||||||
begin
|
|
||||||
if not (AText[i] in ['0'..'9']) then begin
|
'+', '-':
|
||||||
Result := false;
|
;
|
||||||
exit;
|
|
||||||
end;
|
'.', ',':
|
||||||
// If we find the testSep character again it must be a thousand separator.
|
|
||||||
if (AText[i] = testSep) then
|
|
||||||
begin
|
begin
|
||||||
// ... but only if there are 3 numerical digits in between
|
if testSep = #0 then begin
|
||||||
if (testSepPos - i = 4) then
|
testSep := AText[i];
|
||||||
begin
|
testSepPos := i;
|
||||||
fs.ThousandSeparator := testSep;
|
end;
|
||||||
// The decimal separator is the "other" character.
|
// This is the right-most separator candidate in the text
|
||||||
if testSep = '.' then
|
// It can be a decimal or a thousand separator.
|
||||||
fs.DecimalSeparator := ','
|
// Therefore, we continue searching from here.
|
||||||
else
|
dec(i);
|
||||||
fs.DecimalSeparator := '.';
|
while i >= 1 do
|
||||||
done := true;
|
begin
|
||||||
i := 0;
|
if not (AText[i] in ['0'..'9', '+', '-']) then
|
||||||
end else
|
exit;
|
||||||
begin
|
|
||||||
Result := false;
|
// If we find the testSep character again it must be a thousand separator.
|
||||||
exit;
|
if (AText[i] = testSep) then
|
||||||
|
begin
|
||||||
|
// ... but only if there are 3 numerical digits in between
|
||||||
|
if (testSepPos - i = 4) then
|
||||||
|
begin
|
||||||
|
fs.ThousandSeparator := testSep;
|
||||||
|
// The decimal separator is the "other" character.
|
||||||
|
if testSep = '.' then
|
||||||
|
fs.DecimalSeparator := ','
|
||||||
|
else
|
||||||
|
fs.DecimalSeparator := '.';
|
||||||
|
ADecimalSeparator := fs.DecimalSeparator;
|
||||||
|
AThousandSeparator := fs.ThousandSeparator;
|
||||||
|
done := true;
|
||||||
|
i := 0;
|
||||||
|
end else
|
||||||
|
begin
|
||||||
|
Result := false;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
// If we find the "other" separator character, then testSep was a
|
||||||
|
// decimal separator and the current character is a thousand separator.
|
||||||
|
// But there must be 3 digits in between.
|
||||||
|
if AText[i] in ['.', ','] then
|
||||||
|
begin
|
||||||
|
if testSepPos - i <> 4 then // no 3 digits in between --> no number, maybe a date.
|
||||||
|
exit;
|
||||||
|
fs.DecimalSeparator := testSep;
|
||||||
|
fs.ThousandSeparator := AText[i];
|
||||||
|
ADecimalSeparator := fs.DecimalSeparator;
|
||||||
|
AThousandSeparator := fs.ThousandSeparator;
|
||||||
|
done := true;
|
||||||
|
i := 0;
|
||||||
|
end;
|
||||||
|
dec(i);
|
||||||
end;
|
end;
|
||||||
end
|
|
||||||
else
|
|
||||||
// If we find the "other" separator character, then testSep was a
|
|
||||||
// decimal separator and the current character is a thousand separator.
|
|
||||||
if AText[i] in ['.',','] then
|
|
||||||
begin
|
|
||||||
fs.DecimalSeparator := testSep;
|
|
||||||
fs.ThousandSeparator := AText[i];
|
|
||||||
done := true;
|
|
||||||
i := 0;
|
|
||||||
end;
|
end;
|
||||||
dec(i);
|
|
||||||
end;
|
else
|
||||||
end else
|
exit; // Non-numeric character found, no need to continue
|
||||||
if not (AText[i] in ['0'..'9', '+', '-', 'e', 'E', '%']) then
|
|
||||||
begin
|
|
||||||
Result := false;
|
|
||||||
AWarning := '';
|
|
||||||
exit;
|
|
||||||
end;
|
end;
|
||||||
dec(i);
|
dec(i);
|
||||||
end;
|
end;
|
||||||
@@ -1576,9 +1604,10 @@ begin
|
|||||||
// type is found and it is at the third position from the string's end it
|
// type is found and it is at the third position from the string's end it
|
||||||
// might by a thousand separator or a decimal separator. We assume the
|
// might by a thousand separator or a decimal separator. We assume the
|
||||||
// latter case, but create a warning.
|
// latter case, but create a warning.
|
||||||
if Length(AText) - testSepPos = 3 then
|
if (lastDigitPos - testSepPos = 3) and not isPercent then
|
||||||
AWarning := Format(rsAmbiguousDecThouSeparator, [AText]);
|
AWarning := Format(rsAmbiguousDecThouSeparator, [AText]);
|
||||||
fs.DecimalSeparator := testSep;
|
fs.DecimalSeparator := testSep;
|
||||||
|
ADecimalSeparator := fs.DecimalSeparator;
|
||||||
// Make sure that the thousand separator is different from the decimal sep.
|
// Make sure that the thousand separator is different from the decimal sep.
|
||||||
if testSep = '.' then fs.ThousandSeparator := ',' else fs.ThousandSeparator := '.';
|
if testSep = '.' then fs.ThousandSeparator := ',' else fs.ThousandSeparator := '.';
|
||||||
end;
|
end;
|
||||||
@@ -1595,9 +1624,13 @@ begin
|
|||||||
// Try string-to-number conversion
|
// Try string-to-number conversion
|
||||||
Result := TryStrToFloat(AText, ANumber, fs);
|
Result := TryStrToFloat(AText, ANumber, fs);
|
||||||
|
|
||||||
// If successful take care of the percentage sign
|
// If successful ...
|
||||||
if Result and isPercent then
|
if Result then
|
||||||
ANumber := ANumber * 0.01;
|
begin
|
||||||
|
// ... take care of the percentage sign
|
||||||
|
if isPercent then
|
||||||
|
ANumber := ANumber * 0.01;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user