diff --git a/components/fpspreadsheet/fpsnumformatparser.pas b/components/fpspreadsheet/fpsnumformatparser.pas index d191adb18..abd92eb75 100644 --- a/components/fpspreadsheet/fpsnumformatparser.pas +++ b/components/fpspreadsheet/fpsnumformatparser.pas @@ -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; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index fc1535fd6..6540331b1 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -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 + ' '; + el := next; + end; nftDigit: - if IsNumberAt(ASection, el, nf, decs, el) then + if IsNumberAt(ASection, el, nf, decs, next) then + begin Result := Result + ' '; + 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 := '' + 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 := - '' + - sColor + - '' + - ''; - exit; - end; - if IsTokenAt(nftFraction, ASection, next) and - IsNumberAt(ASection, next+1, nf, denom, next) and - (next = Length(Elements)) - then begin - Result := - '' + - sColor + - '' + - ''; - 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 := + '' + + sColor + + ' 0 then + Result := Result + + 'number:min-integer-digits="' + IntToStr(intPart) + '" '; + Result := Result + + 'number:min-numerator-digits="' + IntToStr(numPart) + '" ' + + 'number:min-denominator-digits="' + IntToStr(denomPart) + '" ' + + '/>' + + ''; + 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