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;