fpspreadsheet: Improved fraction format parser

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4079 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-04-08 22:43:57 +00:00
parent bb76adab15
commit 389c4646f5
2 changed files with 235 additions and 164 deletions

View File

@ -31,8 +31,11 @@ type
nftSign, nftSignBracket,
nftDigit, nftOptDigit, nftOptSpaceDigit, nftDecs, nftOptDec,
nftExpChar, nftExpSign, nftExpDigits,
nftPercent,
nftFraction,
nftPercent, // '%'
nftFracSymbol, // '/'
nftFracIntDigit, nftFracIntSpaceDigit, nftFracIntZeroDigit, // '#', '?', '0'
nftFracNumDigit, nftFracNumSpaceDigit, nftFracNumZeroDigit,
nftFracDenomDigit, nftFracDenomSpaceDigit, nftFracDenomZeroDigit,
nftCurrSymbol, nftCountry,
nftColor, nftCompareOp, nftCompareValue,
nftSpace, nftEscaped,
@ -119,16 +122,21 @@ type
// General
procedure CheckSections;
procedure CheckSection(ASection: Integer);
procedure FixMonthMinuteToken(ASection: Integer);
// Format string
function BuildFormatString(ADialect: TsNumFormatDialect): String; virtual;
function BuildFormatStringFromSection(ASection: Integer;
ADialect: TsNumFormatDialect): String; virtual;
// NumberFormat
procedure EvalNumFormatOfSection(ASection: Integer);
function GetTokenIntValueAt(AToken: TsNumFormatToken;
ASection,AIndex: Integer): Integer;
function IsCurrencyAt(ASection: Integer; out ANumFormat: TsNumberFormat;
out ADecimals: byte; out ACurrencySymbol: String; out AColor: TsColor): Boolean;
function IsDateAt(ASection,AIndex: Integer; out ANumberFormat: TsNumberFormat;
var ANextIndex: Integer): Boolean;
function IsFractionAt(ASection,AIndex: Integer;
out AIntPart, ANumPart, ADenomPart, ANextIndex: Integer): Boolean;
function IsNumberAt(ASection,AIndex: Integer; out ANumberFormat: TsNumberFormat;
out ADecimals: Byte; out ANextIndex: Integer): Boolean;
function IsTextAt(AText: string; ASection, AIndex: Integer): Boolean;
@ -308,12 +316,14 @@ begin
if element.TextValue <> '' then result := Result + '"' + element.TextValue + '"';
nftThSep, nftDecSep:
Result := Result + element.TextValue;
nftDigit:
Result := Result + '0';
nftOptDigit, nftOptDec:
Result := Result + '#';
nftOptSpaceDigit:
Result := Result + '#'; /// !!!!!!!!!!! TO BE CHANGED !!!!!!!!!!!!!!!!!
nftDigit, nftFracIntZeroDigit, nftFracNumZeroDigit, nftFracDenomZeroDigit:
Result := Result + DupeString('0', element.IntValue);
nftOptDigit, nftOptDec, nftFracIntDigit, nftFracNumDigit, nftFracDenomDigit:
Result := Result + DupeString('#', element.IntValue);
nftOptSpaceDigit, nftFracIntSpaceDigit, nftFracNumSpaceDigit, nftFracDenomSpaceDigit:
Result := Result + DupeString('?', element.IntValue);
nftFracSymbol:
Result := Result + '/';
nftYear:
Result := Result + DupeString(IfThen(ADialect = nfdExcel, 'Y', 'y'), element.IntValue);
nftMonth:
@ -335,7 +345,7 @@ begin
nftDecs, nftExpDigits, nftMilliseconds:
Result := Result + Dupestring('0', element.IntValue);
nftSpace, nftSign, nftSignBracket, nftExpChar, nftExpSign, nftPercent,
nftFraction, nftAMPM, nftDateTimeSep:
nftAMPM, nftDateTimeSep:
if element.TextValue <> '' then Result := Result + element.TextValue;
nftCurrSymbol:
if element.TextValue <> '' then begin
@ -383,6 +393,37 @@ begin
end;
procedure TsNumFormatParser.CheckSection(ASection: Integer);
begin
FixMonthMinuteToken(ASection);
EvalNumFormatOfSection(ASection);
end;
procedure TsNumFormatParser.ClearAll;
var
i, j: Integer;
begin
for i:=0 to Length(FSections)-1 do begin
for j:=0 to Length(FSections[i].Elements) do
if FSections[i].Elements <> nil then
FSections[i].Elements[j].TextValue := '';
FSections[i].Elements := nil;
FSections[i].CurrencySymbol := '';
end;
FSections := nil;
end;
procedure TsNumFormatParser.DeleteElement(ASection, AIndex: Integer);
var
i, n: Integer;
begin
n := Length(FSections[ASection].Elements);
for i:= AIndex+1 to n-1 do
FSections[ASection].Elements[i-1] := FSections[ASection].Elements[i];
SetLength(FSections[ASection].Elements, n-1);
end;
{ Identify the ambiguous "m" token ("month" or "minute") }
procedure TsNumFormatParser.FixMonthMinuteToken(ASection: Integer);
var
i, j: Integer;
@ -419,7 +460,6 @@ var
end;
begin
// Fix the ambiguous "m":
for i:=0 to High(FSections[ASection].Elements) do
// Find index of nftMonthMinute token...
if FSections[ASection].Elements[i].Token = nftMonthMinute then begin
@ -453,41 +493,6 @@ begin
end;
end;
end;
EvalNumFormatOfSection(ASection);
{
FSections[ASection].NumFormat,
FSections[ASection].Decimals,
FSections[ASection].Numerator,
FSections[ASection].Denominator,
FSections[ASection].CurrencySymbol,
FSections[ASection].Color
);
}
end;
procedure TsNumFormatParser.ClearAll;
var
i, j: Integer;
begin
for i:=0 to Length(FSections)-1 do begin
for j:=0 to Length(FSections[i].Elements) do
if FSections[i].Elements <> nil then
FSections[i].Elements[j].TextValue := '';
FSections[i].Elements := nil;
FSections[i].CurrencySymbol := '';
end;
FSections := nil;
end;
procedure TsNumFormatParser.DeleteElement(ASection, AIndex: Integer);
var
i, n: Integer;
begin
n := Length(FSections[ASection].Elements);
for i:= AIndex+1 to n-1 do
FSections[ASection].Elements[i-1] := FSections[ASection].Elements[i];
SetLength(FSections[ASection].Elements, n-1);
end;
procedure TsNumFormatParser.InsertElement(ASection, AIndex: Integer;
@ -542,22 +547,17 @@ begin
end;
procedure TsNumFormatParser.EvalNumFormatOfSection(ASection: Integer);
{
out ANumFormat: TsNumberFormat; out ADecimals: byte;
out ANumerator, ADenominator: Integer; out ACurrencySymbol: String;
out AColor: TsColor);
}
var
nf, nf1: TsNumberFormat;
next: Integer = 0;
decs, num, denom: Byte;
decs: Byte;
intPart, numPart, denomPart: Integer;
cs: String;
clr: TsColor;
tok: TsNumFormatToken;
begin
nf := nfCustom;
decs := 0;
num := 0;
denom := 0;
cs := '';
clr := scNotDefined;
@ -567,7 +567,7 @@ begin
exit;
end;
// Look for number formats
// Look for number formats (note: fractions are rarely used --> at end)
if IsNumberAt(ASection, 0, nf, decs, next) then begin
// nfFixed, nfFixedTh
if next = Length(Elements) then
@ -583,28 +583,6 @@ begin
FSections[ASection].Decimals := decs;
exit;
end;
// nfFraction
if (IsTokenAt(nftSpace, ASection, next) or IsTextAt(' ', ASection, next)) and
IsNumberAt(ASection, next+1, nf, num, next) and
IsTokenAt(nftFraction, ASection, next) and
IsNumberAt(ASection, next+1, nf, denom, next) and
(next = Length(Elements))
then begin
FSections[ASection].NumFormat := nfFraction;
FSections[ASection].FracInt := integer(decs); // "decs" means "number of integer digits", here
FSections[ASection].FracNumerator := integer(num);
FSections[ASection].FracDenominator := integer(denom);
exit;
end;
if IsTokenAt(nftFraction, ASection, next) and
IsNumberAt(ASection, next+1, nf, denom, next) and
(next = Length(Elements))
then begin
FSections[ASection].NumFormat := nfFraction;
FSections[ASection].FracNumerator := integer(decs);
FSections[ASection].FracDenominator := integer(denom);
exit;
end;
// nfExp
if IsTokenAt(nftExpChar, ASection, next) then begin
if IsTokenAt(nftExpSign, ASection, next+1) and IsTokenAt(nftExpDigits, ASection, next+2) and
@ -653,6 +631,17 @@ begin
FSections[ASection].NumFormat := nf;
exit;
end;
// Look for fractions
if IsFractionAt(ASection, 0, intPart, numPart, denomPart, next) then
if next = Length(Elements) then
begin
FSections[ASection].NumFormat := nfFraction;
FSections[ASection].FracInt := intPart;
FSections[ASection].FracNumerator := numPart;
FSections[ASection].FracDenominator := denomPart;
exit;
end;
end;
// If we get here it must be a custom format.
@ -789,6 +778,15 @@ begin
Result := FSections[AIndex];
end;
function TsNumFormatParser.GetTokenIntValueAt(AToken: TsNumFormatToken;
ASection, AIndex: Integer): Integer;
begin
if IsTokenAt(AToken, ASection, AIndex) then
Result := FSections[ASection].Elements[AIndex].IntValue
else
Result := -1;
end;
{ Checks if a currency-type of format string begins at index AIndex, and returns
the numberformat code, the count of decimals, the currency sambol, and the
color.
@ -969,10 +967,70 @@ begin
Result := false;
end;
function TsNumFormatParser.IsFractionAt(ASection,AIndex: Integer;
out AIntPart, ANumPart, ADenomPart, ANextIndex: Integer): Boolean;
var
tok: TsNumFormatToken;
section: TsNumFormatSection;
begin
Result := false;
AIntPart := 0;
ANumPart := 0;
ADenomPart := 0;
ANextIndex := MaxInt;
if ASection > High(FSections) then
exit;
section := FSections[ASection];
if AIndex > High(section.Elements) then
exit;
// integer part of the fraction
tok := section.Elements[AIndex].Token;
if tok in [nftFracIntDigit, nftFracIntSpaceDigit, nftFracIntZeroDigit] then
begin
AIntPart := section.Elements[AIndex].IntValue;
inc(AIndex);
end;
// Skip space(s)
while (AIndex <= High(section.Elements)) and
(IsTokenAt(nftSpace, ASection, AIndex) or IsTextAt(' ', ASection, AIndex))
do
inc(AIndex);
if AIndex > High(section.Elements) then
exit;
// numerator
tok := section.Elements[AIndex].Token;
if tok in [nftFracNumDigit, nftFracNumSpaceDigit, nftFracNumZeroDigit] then
ANumPart := section.Elements[AIndex].IntValue
else
exit;
// Skip space(s) and fraction symbol
inc(AIndex);
while (AIndex <= High(section.Elements)) and
(IsTokenAt(nftSpace, ASection, AIndex) or
IsTextAt(' ', ASection, AIndex) or
IsTokenAt(nftFracSymbol, ASection, AIndex))
do
inc(AIndex);
// denominator
tok := section.Elements[AIndex].Token;
if tok in [nftFracDenomDigit, nftFracDenomSpaceDigit, nftFracDenomZeroDigit] then
begin
ADenomPart := section.Elements[AIndex].IntValue;
ANextIndex := AIndex + 1;
Result := true;
end;
end;
{ Checks whether the format tokens beginning at AIndex for ASection represent
at standard number format, like nfFixed, nfPercentage etc.
Returns TRUE if it does.
NOTE: ADecimals can have various meanings -- see EvalNumFormatOfSection}
NOTE: Fraction format is not checked here --> see: IsFractionAt }
function TsNumFormatParser.IsNumberAt(ASection,AIndex: Integer;
out ANumberFormat: TsNumberFormat; out ADecimals: Byte;
out ANextIndex: Integer): Boolean;
@ -1003,56 +1061,28 @@ begin
end;
end else
// Now look also for optional digits ('#')
if IsTokenAt(nftOptDigit, ASection, AIndex) then begin // '#'
if IsTokenAt(nftThSep, ASection, AIndex+1) and // ','
IsTokenAt(nftOptDigit, ASection, AIndex+2) and // '#'
IsTokenAt(nftOptDigit, ASection, Aindex+3) and // '#'
IsTokenAt(nftDigit, ASection, AIndex+4) // '0'
if IsTokenAt(nftOptDigit, ASection, AIndex) then begin // '#'
if IsTokenAt(nftThSep, ASection, AIndex+1) and // ','
(GetTokenIntValueAt(nftOptDigit, ASection, AIndex+2) = 2) and // '##'
(GetTokenIntValueAt(nftDigit, ASection, AIndex+3) = 1) // '0'
then begin
if IsTokenAt(nftDecSep, ASection, AIndex+5) and // '.'
IsTokenAt(nftDecs, ASection, AIndex+6) // count of decimals
if IsTokenAt(nftDecSep, ASection, AIndex+4) and // '.'
IsTokenAt(nftDecs, ASection, AIndex+5) // count of decimals
then begin
// This is the case with decimal separator, like "#,##0.000"
Result := true;
ANumberFormat := nfFixedTh;
ADecimals := FSections[ASection].Elements[AIndex+6].IntValue;
ANextIndex := AIndex+7;
ADecimals := FSections[ASection].Elements[AIndex+5].IntValue;
ANextIndex := AIndex + 6;
end else
if not IsTokenAt(nftDecSep, ASection, AIndex+5) then begin
if not IsTokenAt(nftDecSep, ASection, AIndex+4) then begin
// and this is without decimal separator, "#,##0"
result := true;
ANumberFormat := nfFixedTh;
ADecimals := 0;
ANextIndex := AIndex + 5;
ANextIndex := AIndex + 4;
end;
end else
begin // Isolated '#'
result := true;
inc(AIndex);
ANextIndex := AIndex;
ADecimals := 1;
while IsTokenAt(nftOptDigit, ASection, AIndex) do
begin
inc(AIndex);
inc(ANextIndex);
inc(ADecimals);
end;
ANumberFormat := nfFraction;
end;
end else
if IsTokenAt(nftOptSpaceDigit, ASection, AIndex) then // '?'
begin
Result := true;
inc(AIndex);
ANextIndex := AIndex;
ADecimals := 1;
while IsTokenAt(nftOptSpaceDigit, ASection, AIndex) do
begin
inc(AIndex);
inc(ANextIndex);
inc(ADecimals);
end;
ANumberFormat := nfFraction;
end;
end;
@ -1602,9 +1632,12 @@ end;
procedure TsNumFormatParser.ScanNumber;
var
hasDecSep: Boolean;
isFrac: Boolean;
n: Integer;
elem: Integer;
begin
hasDecSep := false;
isFrac := false;
while (FCurrent < FEnd) and (FStatus = psOK) do begin
case FToken of
',': AddElement(nftThSep, ',');
@ -1612,12 +1645,17 @@ begin
AddElement(nftDecSep, '.');
hasDecSep := true;
end;
'0': if hasDecSep then begin
'0': begin
ScanAndCount('0', n);
FToken := PrevToken;
AddElement(nftDecs, n);
end else
AddElement(nftDigit, '0');
if hasDecSep then
AddElement(nftDecs, n)
else
if isFrac then
AddElement(nftFracDenomZeroDigit, n)
else
Addelement(nftDigit, n);
end;
'E', 'e':
begin
AddElement(nftExpChar, FToken);
@ -1633,10 +1671,52 @@ begin
end;
'+', '-':
AddElement(nftSign, FToken);
'#': AddElement(nftOptDigit, FToken);
'?': AddElement(nftOptSpaceDigit, FToken);
'#': begin
ScanAndCount('#', n);
FToken := PrevToken;
if isFrac then
AddElement(nftFracDenomDigit, n)
else
AddElement(nftOptDigit, n);
end;
'?': begin
ScanAndCount('?', n);
FToken := PrevToken;
if isFrac then
AddElement(nftFracDenomSpaceDigit, n)
else
AddElement(nftOptSpaceDigit, n);
end;
'%': AddElement(nftPercent, FToken);
'/': AddElement(nftFraction, FToken);
'/': begin
isFrac := true;
AddElement(nftFracSymbol, FToken);
// go back and replace correct token for numerator (n=0) and integer part (n=1)
n := 0;
elem := High(FSections[FCurrSection].Elements);
while elem > 0 do begin
dec(elem);
case FSections[FCurrSection].Elements[elem].Token of
nftOptDigit:
if n = 0 then
FSections[FCurrSection].Elements[elem].Token := nftFracNumDigit
else
FSections[FCurrSection].Elements[elem].Token := nftFracIntDigit;
nftOptSpaceDigit:
if n = 0 then
FSections[FCurrSection].Elements[elem].Token := nftFracNumSpaceDigit
else
FSections[FCurrSection].Elements[elem].Token := nftFracIntSpaceDigit;
nftDigit:
if n = 0 then
FSections[FCurrSection].Elements[elem].Token := nftFracNumZeroDigit
else
FSections[FCurrSection].Elements[elem].Token := nftFracIntZeroDigit;
end;
inc(n);
end;
end;
else
FToken := PrevToken;
Exit;

View File

@ -315,7 +315,7 @@ end;
function TsSpreadOpenDocNumFormatParser.BuildCurrencyXMLAsString(ASection: Integer): String;
var
el: Integer;
el, next: Integer;
clr: TsColorValue;
nf: TsNumberFormat;
decs: byte;
@ -354,15 +354,21 @@ begin
inc(el);
end;
nftOptDigit:
if IsNumberAt(ASection, el, nf, decs, el) then
if IsNumberAt(ASection, el, nf, decs, next) then
begin
Result := Result +
' <number:number decimal-places="' + IntToStr(decs) +
'" number:min-integer-digits="1" number:grouping="true" />';
el := next;
end;
nftDigit:
if IsNumberAt(ASection, el, nf, decs, el) then
if IsNumberAt(ASection, el, nf, decs, next) then
begin
Result := Result +
' <number:number decimal-places="' + IntToStr(decs) +
'" number:min-integer-digits="1" />';
el := next;
end;
nftRepeat:
begin
if FSections[ASection].Elements[el].TextValue = ' ' then
@ -498,7 +504,7 @@ var
nf : TsNumberFormat;
decs: Byte;
expdig: Integer;
next: Integer;
curr, next: Integer;
sGrouping: String;
sColor: String;
sStyleMap: String;
@ -508,7 +514,7 @@ var
s: String;
isTimeOnly: Boolean;
isInterval: Boolean;
num, denom: byte;
intPart, numPart, denomPart: Integer;
begin
Result := '';
@ -545,14 +551,14 @@ begin
with FSections[ASection] do
begin
next := 0;
curr := 0;
if IsTokenAt(nftColor, ASection, 0) then
begin
clr := FWorkbook.GetPaletteColor(Elements[0].IntValue);
sColor := '<style:text-properties fo:color="' + ColorToHTMLColorStr(clr) + '" />' + LineEnding;
next := 1;
curr := 1;
end;
if IsNumberAt(ASection, next, nf, decs, next) then
if IsNumberAt(ASection, curr, nf, decs, next) then
begin
if nf = nfFixedTh then
sGrouping := 'number:grouping="true" ';
@ -572,39 +578,6 @@ begin
exit;
end;
// nfFraction
if IsTextAt(' ', ASection, next) and
IsNumberAt(ASection, next+1, nf, num, next) and
IsTokenAt(nftFraction, ASection, next) and
IsNumberAt(ASection, next+1, nf, denom, next) and
(next = Length(Elements))
then begin
Result :=
'<number:number-style style:name="' + AFormatName + '">' +
sColor +
'<number:fraction ' +
'number:min-integer-digits="' + IntToStr(decs) + '" ' +
'number:min-numerator-digits="' + IntToStr(num) + '" ' +
'number:min-denominator-digits="' + IntToStr(denom) + '" ' +
'/>' +
'</number:number-style>';
exit;
end;
if IsTokenAt(nftFraction, ASection, next) and
IsNumberAt(ASection, next+1, nf, denom, next) and
(next = Length(Elements))
then begin
Result :=
'<number:number-style style:name="' + AFormatName + '">' +
sColor +
'<number:fraction ' +
'number:min-numerator-digits="' + IntToStr(decs) + '" ' +
'number:min-denominator-digits="' + IntToStr(denom) + '" ' +
'/>' +
'</number:number-style>';
exit;
end;
// nfPercentage
if IsTokenAt(nftPercent, ASection, next) and (next+1 = Length(Elements)) then
begin
@ -647,7 +620,25 @@ begin
end;
end;
// If the program gets here the format can only be nfSci, nfCurrency or date/time.
// nfFraction
if IsFractionAt(ASection, curr, intPart, numPart, denomPart, next)
then begin
Result :=
'<number:number-style style:name="' + AFormatName + '">' +
sColor +
'<number:fraction ';
if intPart > 0 then
Result := Result +
'number:min-integer-digits="' + IntToStr(intPart) + '" ';
Result := Result +
'number:min-numerator-digits="' + IntToStr(numPart) + '" ' +
'number:min-denominator-digits="' + IntToStr(denomPart) + '" ' +
'/>' +
'</number:number-style>';
exit;
end;
// If the program gets here the format can only be Currency or date/time.
el := 0;
decs := 0;
while el < Length(Elements) do