tdf#153993 Extend ODF: lowercase for exponent char
In scientific format, user may want to have clearer format such as:
0.000" "000" "e+" "0
This change
- adds "exponent-lowercase" boolean attribute to scientific-number format to preserve "e" or "E" char
- includes all positions up to exponent for embedded text of scientific number
Add QA test
Change-Id: Ie263f4ecf30a1a8dcd8046e1e048767020e54dc2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153824
Tested-by: Jenkins
Reviewed-by: Laurent Balland <laurent.balland@mailo.fr>
diff --git a/include/xmloff/xmlnumfe.hxx b/include/xmloff/xmlnumfe.hxx
index 64a66a2..8421c5f 100644
--- a/include/xmloff/xmlnumfe.hxx
+++ b/include/xmloff/xmlnumfe.hxx
@@ -71,7 +71,7 @@ private:
bool bGrouping, sal_Int32 nTrailingThousands,
const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries );
SAL_DLLPRIVATE void WriteScientificElement_Impl( sal_Int32 nDecimals, sal_Int32 nMinDecimals, sal_Int32 nInteger, sal_Int32 nBlankInteger,
bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign,
bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign, bool bExponentLowercase,
const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries );
SAL_DLLPRIVATE void WriteFractionElement_Impl( sal_Int32 nInteger, sal_Int32 nBlankInteger, bool bGrouping,
const SvNumberformat& rFormat, sal_uInt16 nPart );
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 066a559..62f3ebc 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -3467,6 +3467,7 @@ namespace xmloff::token {
XML_EXTERNALDATA,
XML_EXPONENT_INTERVAL,
XML_EXPONENT_LOWERCASE,
XML_FORCED_EXPONENT_SIGN,
XML_MIN_DECIMAL_PLACES,
XML_MAX_DENOMINATOR_VALUE,
diff --git a/sc/qa/unit/data/ods/tdf153993-Exponent-lower-case.ods b/sc/qa/unit/data/ods/tdf153993-Exponent-lower-case.ods
new file mode 100644
index 0000000..aaa07ca
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf153993-Exponent-lower-case.ods
Binary files differ
diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx
index b611ceb..2b414142 100644
--- a/sc/qa/unit/subsequent_export_test4.cxx
+++ b/sc/qa/unit/subsequent_export_test4.cxx
@@ -1503,6 +1503,20 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, testEmbeddedTextInDecimal)
lcl_TestNumberFormat(*getScDoc(), "#,##0.000\" \"###\" \"###");
}
CPPUNIT_TEST_FIXTURE(ScExportTest4, testLowercaseExponent)
{
createScDoc("ods/tdf153993-Exponent-lower-case.ods");
// save to ODS and reload
saveAndReload("calc8");
lcl_TestNumberFormat(*getScDoc(), "0.000\" \"000\" \"e+\" \"0");
// save to XLSX and reload
// lower case not preserve in XLSX
saveAndReload("Calc Office Open XML");
lcl_TestNumberFormat(*getScDoc(), "0.000 000 E+ 0");
}
CPPUNIT_TEST_FIXTURE(ScExportTest4, testTotalsRowFunction)
{
createScDoc("xlsx/totalsRowFunction.xlsx");
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index b9fbcfc..c999376 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2845,6 +2845,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
</rng:interleave>
</rng:define>
<!-- TODO no proposal, -->
<rng:define name="number-scientific-number-attlist" combine="interleave">
<rng:optional>
<rng:attribute name="loext:exponent-lowercase">
<rng:ref name="boolean"/>
</rng:attribute>
</rng:optional>
</rng:define>
<!-- TODO no proposal -->
<rng:define name="table-data-pilot-level-attlist" combine="interleave">
<rng:optional>
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index fa439f9..00234904 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -3472,6 +3472,7 @@ namespace xmloff::token {
TOKEN( "external-data", XML_EXTERNALDATA),
TOKEN( "exponent-interval", XML_EXPONENT_INTERVAL ),
TOKEN( "exponent-lowercase", XML_EXPONENT_LOWERCASE ),
TOKEN( "forced-exponent-sign", XML_FORCED_EXPONENT_SIGN ),
TOKEN( "min-decimal-places", XML_MIN_DECIMAL_PLACES ),
TOKEN( "max-denominator-value", XML_MAX_DENOMINATOR_VALUE ),
diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx
index 67675cf..ee09dd0 100644
--- a/xmloff/source/style/xmlnumfe.cxx
+++ b/xmloff/source/style/xmlnumfe.cxx
@@ -695,7 +695,7 @@ void SvXMLNumFmtExport::WriteNumberElement_Impl(
void SvXMLNumFmtExport::WriteScientificElement_Impl(
sal_Int32 nDecimals, sal_Int32 nMinDecimals, sal_Int32 nInteger, sal_Int32 nBlankInteger,
bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign,
bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign, bool bExponentLowercase,
const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries )
{
FinishTextElement_Impl();
@@ -753,6 +753,13 @@ void SvXMLNumFmtExport::WriteScientificElement_Impl(
XML_FORCED_EXPONENT_SIGN,
bExpSign? XML_TRUE : XML_FALSE );
}
// exponent string
// Export only for 1.x with extensions
if (eVersion & SvtSaveOptions::ODFSVER_EXTENDED)
{
if (bExponentLowercase)
m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_EXPONENT_LOWERCASE, XML_TRUE );
}
SvXMLElementExport aElem( m_rExport,
XML_NAMESPACE_NUMBER, XML_SCIENTIFIC_NUMBER,
@@ -1351,6 +1358,7 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
bool bCurrFound = false;
bool bInInteger = true;
bool bExpSign = true;
bool bExponentLowercase = false; // 'e' or 'E' for scientific notation
bool bDecAlign = false; // decimal alignment with "?"
sal_Int32 nExpDigits = 0;
sal_Int32 nIntegerSymbols = 0; // for embedded-text, including "#"
@@ -1421,6 +1429,8 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
if ( pElemStr && ( pElemStr->getLength() == 1
|| ( pElemStr->getLength() == 2 && (*pElemStr)[1] == '-' ) ) )
bExpSign = false; // for 0.00E0 or 0.00E-00
if ( pElemStr && (*pElemStr)[0] == 'e' )
bExponentLowercase = true; // for 0.00e+00
break;
case NF_SYMBOLTYPE_CURRENCY:
bCurrFound = true;
@@ -1459,8 +1469,12 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
// Enable embedded text in decimal part only if there's a decimal part
if ( nPrecision )
nEmbeddedPositionsMax += nPrecision + 1;
// Enable embedded text in exponent in scientific number
if ( nFmtType == SvNumFormatType::SCIENTIFIC )
nEmbeddedPositionsMax += 1 + nExpDigits;
nPos = 0;
bEnd = false;
bExpFound = false;
while (!bEnd)
{
short nElemType = rFormat.GetNumForType( nPart, nPos );
@@ -1475,6 +1489,9 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
if ( pElemStr )
nDigitsPassed += pElemStr->getLength();
break;
case NF_SYMBOLTYPE_EXP:
bExpFound = true;
[[fallthrough]];
case NF_SYMBOLTYPE_DECSEP:
nDigitsPassed++;
break;
@@ -1505,6 +1522,9 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
aEmbeddedEntries.push_back(
SvXMLEmbeddedTextEntry( nPos, nEmbedPos, aEmbeddedStr, bSaveBlankWidthSymbol ));
// exponent sign is required with embedded text in exponent
if ( bExpFound && !bExpSign )
bExpSign = true;
}
break;
}
@@ -1662,7 +1682,7 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
// as integer digits: use nIntegerSymbols instead of nLeading
// nIntegerSymbols represents exponent interval (for engineering notation)
WriteScientificElement_Impl( nPrecision, nMinDecimals, nLeading, nBlankInteger, bThousand, nExpDigits, nIntegerSymbols, bExpSign,
aEmbeddedEntries );
bExponentLowercase, aEmbeddedEntries );
bAnyContent = true;
break;
case SvNumFormatType::FRACTION:
diff --git a/xmloff/source/style/xmlnumfi.cxx b/xmloff/source/style/xmlnumfi.cxx
index f9f9bce..d729149 100644
--- a/xmloff/source/style/xmlnumfi.cxx
+++ b/xmloff/source/style/xmlnumfi.cxx
@@ -103,6 +103,7 @@ struct SvXMLNumberInfo
bool bGrouping = false;
bool bDecReplace = false;
bool bExpSign = true;
bool bExponentLowercase = false; /// Exponent is 'e' instead of 'E'
bool bDecAlign = false;
double fDisplayFactor = 1.0;
OUString aIntegerFractionDelimiter;
@@ -722,6 +723,11 @@ SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport,
if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
aNumInfo.bExpSign = bAttrBool;
break;
case XML_ELEMENT(NUMBER, XML_EXPONENT_LOWERCASE):
case XML_ELEMENT(LO_EXT, XML_EXPONENT_LOWERCASE):
if (::sax::Converter::convertBool( bAttrBool, aIter.toView() ))
aNumInfo.bExponentLowercase = bAttrBool;
break;
case XML_ELEMENT(NUMBER, XML_MIN_NUMERATOR_DIGITS):
if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
aNumInfo.nMinNumerDigits = nAttrVal;
@@ -1173,16 +1179,7 @@ void SvXMLNumFmtElementContext::endFastElement(sal_Int32 )
rParent.AddToCode( '#' );
}
}
rParent.AddNumber( aNumInfo ); // simple number
if ( aNumInfo.bExpSign )
rParent.AddToCode( u"E+" );
else
rParent.AddToCode( u"E" );
for (sal_Int32 i=0; i<aNumInfo.nExpDigits; i++)
{
rParent.AddToCode( '0' );
}
rParent.AddNumber( aNumInfo ); // number and exponent
}
break;
@@ -1868,6 +1865,23 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
aNumStr.append( cAdd );
}
// Scientific number
sal_Int32 nExpPos = -1;
if ( rInfo.nExpDigits > 0 )
{
nExpPos = aNumStr.getLength();
aNumStr.append( rInfo.bExponentLowercase ? u"e" : u"E" );
// exponent sign is required with embedded text in exponent
if ( rInfo.bExpSign || ( nEmbeddedCount && ( rInfo.nDecimals + 1 < -rInfo.m_EmbeddedElements.begin()->first ) ) )
{
aNumStr.append( u"+" );
}
for (sal_Int32 i=0; i<rInfo.nExpDigits; i++)
{
aNumStr.append( '0' );
}
}
if ( nEmbeddedCount )
{
// insert embedded strings into number string
@@ -1893,6 +1907,8 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
aNumStr.insert(0, '#');
}
nZeroPos = nZeroPos + nAddCount;
if ( nExpPos > 0 )
nExpPos = nExpPos + nAddCount;
}
// m_EmbeddedElements is sorted with ascending positions - loop is from right to left
@@ -1900,7 +1916,9 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
{
sal_Int32 const nFormatPos = it.first;
sal_Int32 nInsertPos = nZeroPos - nFormatPos;
if ( nInsertPos >= 0 )
if ( nExpPos > 0 && nInsertPos > nExpPos )
nInsertPos ++;
if ( 0 <= nInsertPos && nInsertPos <= aNumStr.getLength() )
{
aNumStr.insert( nInsertPos, it.second );
}
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index 85d9473..cdd3877 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -3228,6 +3228,7 @@ display-units
display-units-built-in-unit
external-data
exponent-interval
exponent-lowercase
forced-exponent-sign
min-decimal-places
max-denominator-value