diff --git a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr index 19a0fb20d..9769918ce 100644 --- a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr +++ b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr @@ -19,6 +19,7 @@ var MyDir: string; number1, number2, number3, number4, number5, number6, number7, number8: Double; + dt1, dt2: TDateTime; row: Integer = 7; begin MyDir := ExtractFilePath(ParamStr(0)); @@ -31,6 +32,9 @@ begin number7 := 1/number3; number8 := -1/number3; + dt1 := EncodeDate(2012, 1, 1) + EncodeTime(9, 1, 2, 12); + dt2 := EncodeDate(2012, 12, 1) + EncodeTime(21, 1, 2, 12); + // Create the spreadsheet MyWorkbook := TsWorkbook.Create; MyWorksheet := MyWorkbook.AddWorksheet('My Worksheet'); @@ -230,6 +234,65 @@ begin MyWorksheet.WriteCurrency(row, 6, number6, nfAccountingRed, 2, 'EUR', pcfCSV, ncfMCSV); MyWorksheet.WriteCurrency(row, 7, number7, nfAccountingRed, 2, 'EUR', pcfCSV, ncfMCSV); MyWorksheet.WriteCurrency(row, 8, number8, nfAccountingRed, 2, 'EUR', pcfCSV, ncfMCSV); + inc(row,2); + MyWorksheet.WriteUTF8Text(row, 0, 'Some date/time values in various formats:'); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfShortDateTime'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfShortDateTime); + MyWorksheet.WriteDateTime(row, 2, dt2, nfShortDateTime); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfShortDate'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfShortDate); + MyWorksheet.WriteDateTime(row, 2, dt2, nfShortDate); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfLongDate'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfLongDate); + MyWorksheet.WriteDateTime(row, 2, dt2, nfLongDate); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfShortTime'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfShortTime); + MyWorksheet.WriteDateTime(row, 2, dt2, nfShortTime); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfLongTime'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfLongTime); + MyWorksheet.WriteDateTime(row, 2, dt2, nfLongTime); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfShortTimeAM'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfShortTimeAM); + MyWorksheet.WriteDateTime(row, 2, dt2, nfShortTimeAM); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfLongTimeAM'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfLongTimeAM); + MyWorksheet.WriteDateTime(row, 2, dt2, nfLongTimeAM); + inc(row); + // In order to use a semicolon as a date-time separator it must be escaped either by + // using the backslash or quotes (because the semicolon is the separator between sections) + MyWorksheet.WriteUTF8Text(row, 0, 'nfCustom, dddd, dd/mm/yyyy\; hh:nn'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfCustom, 'dddd, dd/mm/yyyy\; hh:nn'); + MyWorksheet.WriteDateTime(row, 2, dt2, nfCustom, 'dddd, dd/mm/yyyy\; hh:nn'); + MyWorksheet.WriteUTF8Text(row, 3, 'The semicolon must be escaped otherwise it would be misunderstood as a section separator.'); + MyWorksheet.WriteUTF8Text(row, 4, 'This format is not displayed correctly by Open/LibreOffice.'); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfCustom, dddd, dd/mm/yyyy"; "hh:nn'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfCustom, 'dddd, dd/mm/yyyy"; "hh:nn'); + MyWorksheet.WriteDateTime(row, 2, dt2, nfCustom, 'dddd, dd/mm/yyyy"; "hh:nn'); + MyWorksheet.WriteUTF8Text(row, 3, 'The semicolon must be escaped otherwise it would be misunderstood as a section separator.'); + MyWorksheet.WriteUTF8Text(row, 4, 'This format is not displayed correctly by Open/LibreOffice.'); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfCustom, dd/mmm'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfCustom, 'dd/mmm'); + MyWorksheet.WriteDateTime(row, 2, dt2, nfCustom, 'dd/mmm'); + MyWorksheet.WriteUTF8Text(row, 3, 'The slash is replaced by the date or time separator of the FormatSettings'); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfCustom, mmm/yy'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfCustom, 'mmm/yy'); + MyWorksheet.WriteDateTime(row, 2, dt2, nfCustom, 'mmm/yy'); + MyWorksheet.WriteUTF8Text(row, 3, 'The slash is replaced by the date or time separator of the FormatSettings'); + inc(row); + MyWorksheet.WriteUTF8Text(row, 0, 'nfCustom, mmm-yy'); + MyWorksheet.WriteDateTime(row, 1, dt1, nfCustom, 'mmm-yy'); + MyWorksheet.WriteDateTime(row, 2, dt2, nfCustom, 'mmm-yy'); + MyWorksheet.WriteUTF8Text(row, 3, 'The dash is used literally'); // Creates a new worksheet MyWorksheet := MyWorkbook.AddWorksheet('My Worksheet 2'); diff --git a/components/fpspreadsheet/fpsnumformatparser.pas b/components/fpspreadsheet/fpsnumformatparser.pas index f2b15c74d..2abfc46e9 100644 --- a/components/fpspreadsheet/fpsnumformatparser.pas +++ b/components/fpspreadsheet/fpsnumformatparser.pas @@ -275,12 +275,16 @@ function TsNumFormatParser.AnalyzeCurrency(const AValue: String): Boolean; var uValue: String; begin - uValue := Uppercase(AValue); - Result := (uValue = Uppercase(AnsiToUTF8(FWorkbook.FormatSettings.CurrencyString))) or - (uValue = '$') or (uValue = 'USD') or - (uValue = '€') or (uValue = 'EUR') or - (uValue = '£') or (uValue = 'GBP') or - (uValue = '¥') or (uValue = 'JPY'); + if (FWorkbook = nil) or (FWorkbook.FormatSettings.CurrencyString = '') then + Result := false + else begin + uValue := Uppercase(AValue); + Result := (uValue = Uppercase(AnsiToUTF8(FWorkbook.FormatSettings.CurrencyString))) or + (uValue = '$') or (uValue = 'USD') or + (uValue = '€') or (uValue = 'EUR') or + (uValue = '£') or (uValue = 'GBP') or + (uValue = '¥') or (uValue = 'JPY'); + end; end; { Creates a formatstring for all sections. @@ -1528,6 +1532,11 @@ begin end; 'A', 'a': ScanAMPM; + ',', '-': + begin + Addelement(nftText, FToken); + FToken := NextToken; + end else // char pointer must be at end of date/time mask. FToken := PrevToken; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 97bb0316a..bb87baf3c 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -343,9 +343,9 @@ begin AIsTimeOnly := false; case Elements[el].IntValue of 1: s := ''; - 2: s := 'number:style="long"'; - 3: s := 'number:textual="true"'; - 4: s := 'number:style="long" number:textual="true"'; + 2: s := 'number:style="long" '; + 3: s := 'number:textual="true" '; + 4: s := 'number:style="long" number:textual="true" '; end; Result := result + AIndent + ' ' + LineEnding; @@ -357,11 +357,9 @@ begin AIsTimeOnly := false; case Elements[el].IntValue of 1: s := 'day '; - 2: s := 'day number:style="long"'; - 3: s := 'day number:textual="true"'; - 4: s := 'day number:style="long" number:textual="true"'; - 5: s := 'day-of-week '; - 6: s := 'day-of-week number:style="long"'; + 2: s := 'day number:style="long" '; + 3: s := 'day-of-week '; + 4: s := 'day-of-week number:style="long" '; end; Result := Result + AIndent + ' ' + LineEnding; @@ -1581,57 +1579,47 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode); end else if nodeName = 'number:year' then begin s := GetAttrValue(node, 'number:style'); - if s = 'long' then fmt := fmt + 'yyyy' - else if s = '' then fmt := fmt + 'yy'; + fmt := fmt + IfThen(s = 'long', 'yyyy', 'yy'); end else if nodeName = 'number:month' then begin s := GetAttrValue(node, 'number:style'); stxt := GetAttrValue(node, 'number:textual'); - if (stxt = 'true') then begin // Month as text - if (s = 'long') then fmt := fmt + 'mmmm' else fmt := fmt + 'mmm'; - end else begin // Month as number - if (s = 'long') then fmt := fmt + 'mm' else fmt := fmt + 'm'; - end; + if (stxt = 'true') then // Month as text + fmt := fmt + IfThen(s = 'long', 'mmmm', 'mmm') + else // Month as number + fmt := fmt + IfThen(s = 'long', 'mm', 'm'); end else if nodeName = 'number:day' then begin s := GetAttrValue(node, 'number:style'); - stxt := GetAttrValue(node, 'number:textual'); - if (stxt = 'true') then begin // day as text - if (s = 'long') then fmt := fmt + 'dddd' else fmt := fmt + 'ddd'; - end else begin // day as number - if (s = 'long') then fmt := fmt + 'dd' else fmt := fmt + 'd'; - end; - end; + fmt := fmt + IfThen(s = 'long', 'dd', 'd'); + end else if nodeName = 'number:day-of-week' then begin - s := GetAttrValue(node, 'number:stye'); - if (s = 'long') then fmt := fmt + 'dddddd' else fmt := fmt + 'ddddd'; + s := GetAttrValue(node, 'number:style'); + fmt := fmt + IfThen(s = 'long', 'dddd', 'ddd'); end else if nodeName = 'number:hours' then begin s := GetAttrValue(node, 'number:style'); - if (sovr = 'false') then begin - if (s = 'long') then fmt := fmt + '[hh]' else fmt := fmt + '[h]'; - end else begin - if (s = 'long') then fmt := fmt + 'hh' else fmt := fmt + 'h'; - end; + if (sovr = 'false') then + fmt := fmt + IfThen(s = 'long', '[hh]', '[h]') + else + fmt := fmt + IfThen(s = 'long', 'hh', 'h'); sovr := ''; end else if nodeName = 'number:minutes' then begin s := GetAttrValue(node, 'number:style'); - if (sovr = 'false') then begin - if (s = 'long') then fmt := fmt + '[nn]' else fmt := fmt + '[n]'; - end else begin - if (s = 'long') then fmt := fmt + 'nn' else fmt := fmt + 'n'; - end; + if (sovr = 'false') then + fmt := fmt + IfThen(s = 'long', '[nn]', '[n]') + else + fmt := fmt + IfThen(s = 'long', 'nn', 'n'); sovr := ''; end else if nodeName = 'number:seconds' then begin s := GetAttrValue(node, 'number:style'); - if (sovr = 'false') then begin - if (s = 'long') then fmt := fmt + '[ss]' else fmt := fmt + '[s]'; - end else begin - if (s = 'long') then fmt := fmt + 'ss' else fmt := fmt + 's'; - sovr := ''; - end; + if (sovr = 'false') then + fmt := fmt + IfThen(s = 'long', '[ss]', '[s]') + else + fmt := fmt + IfThen(s = 'long', 'ss', 's'); + sovr := ''; s := GetAttrValue(node, 'number:decimal-places'); if (s <> '') and (s <> '0') then fmt := fmt + '.' + DupeString('0', StrToInt(s)); @@ -1641,8 +1629,14 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode); else if nodeName = 'number:text' then begin childnode := node.FirstChild; - if childnode <> nil then - fmt := fmt + childnode.NodeValue; + if childnode <> nil then begin + s := childNode.NodeValue; + if pos(';', s) > 0 then + fmt := fmt + '"' + s + '"' + // avoid "misunderstanding" the semicolon as a section separator! + else + fmt := fmt + childnode.NodeValue; + end; end; node := node.NextSibling; end; diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index d47f8c515..18e4636e3 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -1007,6 +1007,16 @@ end; procedure SplitFormatString(const AFormatString: String; out APositivePart, ANegativePart, AZeroPart: String); + + procedure AddToken(AToken: Char; AWhere:Byte); + begin + case AWhere of + 0: APositivePart := APositivePart + AToken; + 1: ANegativePart := ANegativePart + AToken; + 2: AZeroPart := AZeroPart + AToken; + end; + end; + var P, PStart, PEnd: PChar; token: Char; @@ -1025,24 +1035,28 @@ begin while P < PEnd do begin token := P^; case token of - '"': begin // Skip quoted strings + '"': begin // Let quoted text intact + AddToken(token, where); inc(P); token := P^; while (P < PEnd) and (token <> '"') do begin + AddToken(token, where); inc(P); token := P^; end; + AddToken(token, where); end; ';': begin // Separator between parts inc(where); if where = 3 then exit; - end - else case where of - 0: APositivePart := APositivePart + token; - 1: ANegativePart := ANegativePart + token; - 2: AZeroPart := AZeroPart + token; end; + '\': begin // Skip "Escape" character and add next char immediately + inc(P); + token := P^; + AddToken(token, where); + end; + else AddToken(token, where); end; inc(P); end;