Kaydet (Commit) c52f3ea0 authored tarafından Eike Rathke's avatar Eike Rathke

user selectable string conversion models, related fdo#37132 fdo#74622

Determines how to treat text when encountered as operand in an arithmetic
operation or as argument to a function that expects a number instead.

Selectable under Tools->Options->Calc->Formula "Detailed calculation settings"
"Custom" from "Conversion from text to number" are:

Generate #VALUE! error:         =1+"1" or =1+"x" give #VALUE!
Treat as zero:                  =1+"1" or =1+"x" give 1
Convert only unambiguous:       =1+"1" gives 2, but =1+"1.000" or =1+"x" give #VALUE!
Convert also locale dependent:  =1+"1.000" may be 2 or 1001 ... =1+"x" gives #VALUE!

For "Generate #VALUE! error" and "Treat as zero" the "Treat empty string as
zero" option follows these settings, for "Convert only unambiguous" and
"Convert also locale dependent" it can be set independently.

When reading documents created by other spreadsheet applications or older
versions of LibreOffice, and to interchange documents between different locales
the "Convert only unambiguous" with "Treat empty string as zero = True" setting
is recommended, though LibreOffice so far acted as "Convert also locale
dependent" with "Treat empty string as zero = False", which is the reason that
option is kept as default.

The best setting to create new documents that can be interpreted by all
spreadsheet applications without on-the-fly string conversion is
"Generate #VALUE! error". Not having to convert strings during
calculation ist also faster, of course.

Change-Id: Ie6dc34a00a82064a2d862b2178ce715fab945f85
üst cb87c1ee
......@@ -28,7 +28,17 @@ enum ScRecalcOptions
*/
struct SC_DLLPUBLIC ScCalcConfig
{
// from most stringent to most relaxed
enum StringConversion
{
STRING_CONVERSION_AS_ERROR = 0, ///< =1+"1" or =1+"x" give #VALUE!
STRING_CONVERSION_AS_ZERO, ///< =1+"1" or =1+"x" give 1
STRING_CONVERSION_UNAMBIGUOUS, ///< =1+"1" gives 2, but =1+"1.000" or =1+"x" give #VALUE!
STRING_CONVERSION_LOCALE_DEPENDENT ///< =1+"1.000" may be 2 or 1001 ... =1+"x" gives #VALUE!
};
formula::FormulaGrammar::AddressConvention meStringRefAddressSyntax;
StringConversion meStringConversion;
bool mbEmptyStringAsZero:1;
bool mbOpenCLEnabled:1;
bool mbOpenCLAutoSelect:1;
......
......@@ -11,6 +11,7 @@
ScCalcConfig::ScCalcConfig() :
meStringRefAddressSyntax(formula::FormulaGrammar::CONV_UNSPECIFIED),
meStringConversion(STRING_CONVERSION_LOCALE_DEPENDENT), // old LibreOffice behavior
mbEmptyStringAsZero(false),
mbOpenCLEnabled(false),
mbOpenCLAutoSelect(true)
......@@ -25,6 +26,7 @@ void ScCalcConfig::reset()
bool ScCalcConfig::operator== (const ScCalcConfig& r) const
{
return meStringRefAddressSyntax == r.meStringRefAddressSyntax &&
meStringConversion == r.meStringConversion &&
mbEmptyStringAsZero == r.mbEmptyStringAsZero &&
mbOpenCLEnabled == r.mbOpenCLEnabled &&
mbOpenCLAutoSelect == r.mbOpenCLAutoSelect &&
......
......@@ -193,13 +193,14 @@ SfxPoolItem* ScTpFormulaItem::Clone( SfxItemPool * ) const
#define SCFORMULAOPT_SEP_ARRAY_ROW 3
#define SCFORMULAOPT_SEP_ARRAY_COL 4
#define SCFORMULAOPT_STRING_REF_SYNTAX 5
#define SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO 6
#define SCFORMULAOPT_OOXML_RECALC 7
#define SCFORMULAOPT_ODF_RECALC 8
#define SCFORMULAOPT_OPENCL_ENABLED 9
#define SCFORMULAOPT_OPENCL_AUTOSELECT 10
#define SCFORMULAOPT_OPENCL_DEVICE 11
#define SCFORMULAOPT_COUNT 12
#define SCFORMULAOPT_STRING_CONVERSION 6
#define SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO 7
#define SCFORMULAOPT_OOXML_RECALC 8
#define SCFORMULAOPT_ODF_RECALC 9
#define SCFORMULAOPT_OPENCL_ENABLED 10
#define SCFORMULAOPT_OPENCL_AUTOSELECT 11
#define SCFORMULAOPT_OPENCL_DEVICE 12
#define SCFORMULAOPT_COUNT 13
Sequence<OUString> ScFormulaCfg::GetPropertyNames()
{
......@@ -211,6 +212,7 @@ Sequence<OUString> ScFormulaCfg::GetPropertyNames()
"Syntax/SeparatorArrayRow", // SCFORMULAOPT_SEP_ARRAY_ROW
"Syntax/SeparatorArrayCol", // SCFORMULAOPT_SEP_ARRAY_COL
"Syntax/StringRefAddressSyntax", // SCFORMULAOPT_STRING_REF_SYNTAX
"Syntax/StringConversion", // SCFORMULAOPT_STRING_CONVERSION
"Syntax/EmptyStringAsZero", // SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO
"Load/OOXMLRecalcMode", // SCFORMULAOPT_OOXML_RECALC
"Load/ODFRecalcMode", // SCFORMULAOPT_ODF_RECALC
......@@ -229,7 +231,7 @@ Sequence<OUString> ScFormulaCfg::GetPropertyNames()
ScFormulaCfg::PropsToIds ScFormulaCfg::GetPropNamesToId()
{
Sequence<OUString> aPropNames = GetPropertyNames();
static sal_uInt16 aVals[] = { SCFORMULAOPT_GRAMMAR, SCFORMULAOPT_ENGLISH_FUNCNAME, SCFORMULAOPT_SEP_ARG, SCFORMULAOPT_SEP_ARRAY_ROW, SCFORMULAOPT_SEP_ARRAY_COL, SCFORMULAOPT_STRING_REF_SYNTAX, SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO, SCFORMULAOPT_OOXML_RECALC, SCFORMULAOPT_ODF_RECALC, SCFORMULAOPT_OPENCL_ENABLED, SCFORMULAOPT_OPENCL_AUTOSELECT, SCFORMULAOPT_OPENCL_DEVICE };
static sal_uInt16 aVals[] = { SCFORMULAOPT_GRAMMAR, SCFORMULAOPT_ENGLISH_FUNCNAME, SCFORMULAOPT_SEP_ARG, SCFORMULAOPT_SEP_ARRAY_ROW, SCFORMULAOPT_SEP_ARRAY_COL, SCFORMULAOPT_STRING_REF_SYNTAX, SCFORMULAOPT_STRING_CONVERSION, SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO, SCFORMULAOPT_OOXML_RECALC, SCFORMULAOPT_ODF_RECALC, SCFORMULAOPT_OPENCL_ENABLED, SCFORMULAOPT_OPENCL_AUTOSELECT, SCFORMULAOPT_OPENCL_DEVICE };
OSL_ENSURE( SAL_N_ELEMENTS(aVals) == aPropNames.getLength(), "Properties and ids are out of Sync");
PropsToIds aPropIdMap;
for ( sal_uInt16 i=0; i<aPropNames.getLength(); ++i )
......@@ -328,7 +330,7 @@ void ScFormulaCfg::UpdateFromProperties( const Sequence<OUString>& aNames )
do
{
if (!(pValues[nProp] >>= nIntVal))
// extractino failed.
// extraction failed.
break;
switch (nIntVal)
......@@ -353,6 +355,39 @@ void ScFormulaCfg::UpdateFromProperties( const Sequence<OUString>& aNames )
GetCalcConfig().meStringRefAddressSyntax = eConv;
}
break;
case SCFORMULAOPT_STRING_CONVERSION:
{
// Get default value in case this option is not set.
ScCalcConfig::StringConversion eConv = GetCalcConfig().meStringConversion;
do
{
if (!(pValues[nProp] >>= nIntVal))
// extraction failed.
break;
switch (nIntVal)
{
case 0:
eConv = ScCalcConfig::STRING_CONVERSION_AS_ERROR;
break;
case 1:
eConv = ScCalcConfig::STRING_CONVERSION_AS_ZERO;
break;
case 2:
eConv = ScCalcConfig::STRING_CONVERSION_UNAMBIGUOUS;
break;
case 3:
eConv = ScCalcConfig::STRING_CONVERSION_LOCALE_DEPENDENT;
break;
default:
SAL_WARN("sc", "unknown string conversion option!");
}
}
while (false);
GetCalcConfig().meStringConversion = eConv;
}
break;
case SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO:
{
sal_Bool bVal = GetCalcConfig().mbEmptyStringAsZero;
......@@ -492,6 +527,19 @@ void ScFormulaCfg::Commit()
pValues[nProp] <<= nVal;
}
break;
case SCFORMULAOPT_STRING_CONVERSION:
{
sal_Int32 nVal = 3;
switch (GetCalcConfig().meStringConversion)
{
case ScCalcConfig::STRING_CONVERSION_AS_ERROR: nVal = 0; break;
case ScCalcConfig::STRING_CONVERSION_AS_ZERO: nVal = 1; break;
case ScCalcConfig::STRING_CONVERSION_UNAMBIGUOUS: nVal = 2; break;
case ScCalcConfig::STRING_CONVERSION_LOCALE_DEPENDENT: nVal = 3; break;
}
pValues[nProp] <<= nVal;
}
break;
case SCFORMULAOPT_EMPTY_OUSTRING_AS_ZERO:
{
sal_Bool bVal = GetCalcConfig().mbEmptyStringAsZero;
......
......@@ -189,6 +189,25 @@ sal_uInt16 ScInterpreter::GetCellErrCode( const ScRefCellValue& rCell )
return rCell.meType == CELLTYPE_FORMULA ? rCell.mpFormula->GetErrCode() : 0;
}
namespace
{
bool isEmptyString( const OUString& rStr )
{
if (rStr.isEmpty())
return true;
else if (rStr[0] == ' ')
{
const sal_Unicode* p = rStr.getStr() + 1;
const sal_Unicode* const pStop = p - 1 + rStr.getLength();
while (p < pStop && *p == ' ')
++p;
if (p == pStop)
return true;
}
return false;
}
}
/** Convert string content to numeric value.
Converted are only integer numbers including exponent, and ISO 8601 dates
......@@ -221,9 +240,9 @@ sal_uInt16 ScInterpreter::GetCellErrCode( const ScRefCellValue& rCell )
double ScInterpreter::ConvertStringToValue( const OUString& rStr )
{
#if 1
// We keep this code until we provide a friendly way to convert string
// numbers into numbers in the UI.
// We keep ScCalcConfig::STRING_CONVERSION_LOCALE_DEPENDENT default until
// we provide a friendly way to convert string numbers into numbers in the UI.
double fValue = 0.0;
if (mnStringNoValueError == errCellNoValue)
{
......@@ -232,40 +251,51 @@ double ScInterpreter::ConvertStringToValue( const OUString& rStr )
return fValue;
}
if (GetGlobalConfig().mbEmptyStringAsZero)
switch (GetGlobalConfig().meStringConversion)
{
// The number scanner does not accept empty strings or strings
// containing only spaces, be on par in these cases with what was
// accepted in OOo and is in AOO (see also the else branch below) and
// convert to 0 to prevent interoperability nightmares.
if (rStr.isEmpty())
case ScCalcConfig::STRING_CONVERSION_AS_ERROR:
SetError( mnStringNoValueError);
return fValue;
else if (rStr[0] == ' ')
{
const sal_Unicode* p = rStr.getStr() + 1;
const sal_Unicode* const pStop = p - 1 + rStr.getLength();
while (p < pStop && *p == ' ')
++p;
if (p == pStop)
case ScCalcConfig::STRING_CONVERSION_AS_ZERO:
return fValue;
case ScCalcConfig::STRING_CONVERSION_LOCALE_DEPENDENT:
{
if (GetGlobalConfig().mbEmptyStringAsZero)
{
// The number scanner does not accept empty strings or strings
// containing only spaces, be on par in these cases with what was
// accepted in OOo and is in AOO (see also the
// STRING_CONVERSION_UNAMBIGUOUS branch) and convert to 0 to prevent
// interoperability nightmares.
if (isEmptyString( rStr))
return fValue;
}
sal_uInt32 nFIndex = 0;
if (!pFormatter->IsNumberFormat(rStr, nFIndex, fValue))
{
SetError( mnStringNoValueError);
fValue = 0.0;
}
return fValue;
}
}
break;
case ScCalcConfig::STRING_CONVERSION_UNAMBIGUOUS:
{
if (!GetGlobalConfig().mbEmptyStringAsZero)
{
if (isEmptyString( rStr))
{
SetError( mnStringNoValueError);
return fValue;
}
}
}
// continue below, pulled from switch case for better readability
break;
}
sal_uInt32 nFIndex = 0;
if (!pFormatter->IsNumberFormat(rStr, nFIndex, fValue))
{
SetError( mnStringNoValueError);
fValue = 0.0;
}
return fValue;
#else
double fValue = 0.0;
if (mnStringNoValueError == errCellNoValue)
{
// Requested that all strings result in 0, error handled by caller.
SetError( mnStringNoValueError);
return fValue;
}
OUString aStr( rStr);
rtl_math_ConversionStatus eStatus;
sal_Int32 nParseEnd;
......@@ -305,14 +335,14 @@ double ScInterpreter::ConvertStringToValue( const OUString& rStr )
p = pStart;
while (p < pStop && *p == ' ')
++p;
if (p < pStop && !CharClass::isAsciiDigit(*p))
if (p < pStop && !rtl::isAsciiDigit(*p))
SetError( mnStringNoValueError);
p = pLastStart;
while (p < pStop && !nGlobalError && eState < blank)
{
if (eState == minute)
nCurFmtType |= NUMBERFORMAT_TIME;
if (CharClass::isAsciiDigit(*p))
if (rtl::isAsciiDigit(*p))
{
// Maximum 2 digits per unit, except fractions.
if (p - pLastStart >= 2 && eState != fraction)
......@@ -427,7 +457,6 @@ double ScInterpreter::ConvertStringToValue( const OUString& rStr )
fValue = 0.0;
}
return fValue;
#endif
}
double ScInterpreter::GetCellValue( const ScAddress& rPos, ScRefCellValue& rCell )
......
......@@ -50,6 +50,7 @@ private:
#endif
OUString toString(formula::FormulaGrammar::AddressConvention eConv) const;
OUString toString(ScCalcConfig::StringConversion eConv) const;
OUString toString(bool bVal) const;
SvTreeListEntry *createBoolItem(const OUString &rCaption, bool bValue) const;
void setValueAt(size_t nPos, const OUString &rString);
......@@ -81,6 +82,14 @@ private:
OUString maDescStringRefSyntax;
OUString maUseFormulaSyntax;
OUString maStringConversionAsError;
OUString maStringConversionAsZero;
OUString maStringConversionUnambiguous;
OUString maStringConversionLocaleDependent;
OUString maCaptionStringConversion;
OUString maDescStringConversion;
OUString maCaptionEmptyStringAsZero;
OUString maDescEmptyStringAsZero;
......@@ -93,6 +102,8 @@ private:
#if HAVE_FEATURE_OPENCL
std::vector<sc::OpenclPlatformInfo> maPlatformInfo;
#endif
bool mbSelectedEmptyStringAsZero;
};
#endif
......
......@@ -188,10 +188,10 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="ref_syntax_caption">
<object class="GtkLabel" id="string_conversion_caption">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Reference syntax for string reference</property>
<property name="label" translatable="yes">Conversion from text to number</property>
</object>
<packing>
<property name="left_attach">0</property>
......@@ -201,10 +201,10 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="ref_syntax_desc">
<object class="GtkLabel" id="string_conversion_desc">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Formula syntax to use when parsing references given in string parameters. This affects built-in functions such as INDIRECT that takes a reference as a string value.</property>
<property name="label" translatable="yes">How to treat text when encountered as operand in an arithmetic operation or as argument to a function that expects a number instead. Unambiguous conversion is possible for integer numbers including exponents and ISO 8601 dates and times in their extended formats with separators. Fractional numeric values with decimal separators or dates other than ISO 8601 are locale dependent. Note that in locale dependent conversions the resulting numeric value may differ between locales!</property>
<property name="wrap">True</property>
<property name="max_width_chars">56</property>
</object>
......@@ -216,10 +216,10 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="use_formula_syntax">
<object class="GtkLabel" id="string_conversion_as_error">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Use formula syntax</property>
<property name="label" translatable="yes">Generate #VALUE! error</property>
</object>
<packing>
<property name="left_attach">0</property>
......@@ -228,6 +228,45 @@
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="string_conversion_as_zero">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Treat as zero</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="string_conversion_unambiguous">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Convert only unambiguous</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="string_conversion_locale_dependent">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Convert also locale dependent</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="empty_str_as_zero_caption">
<property name="can_focus">False</property>
......@@ -236,7 +275,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="top_attach">10</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
......@@ -245,13 +284,54 @@
<object class="GtkLabel" id="empty_str_as_zero_desc">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">This option determines whether or not an empty string is to be treated as having a value of zero when used in arithmetic.</property>
<property name="label" translatable="yes">This option determines whether an empty string is to be treated as having a value of zero when used in arithmetic or generates an error. It is disabled if conversion from text to number is set to always generate an error or always treat text as zero and then follows that value.</property>
<property name="wrap">True</property>
<property name="max_width_chars">56</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
<property name="top_attach">11</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ref_syntax_caption">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Reference syntax for string reference</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ref_syntax_desc">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Formula syntax to use when parsing references given in string parameters. This affects built-in functions such as INDIRECT that takes a reference as a string value.</property>
<property name="wrap">True</property>
<property name="max_width_chars">56</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">13</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="use_formula_syntax">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Use formula syntax</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">14</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
......@@ -264,7 +344,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
<property name="top_attach">15</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
......@@ -279,7 +359,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
<property name="top_attach">16</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
......@@ -470,7 +550,7 @@
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">11</property>
<property name="top_attach">17</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment