basic/qa/vba_tests/format.vb | 2 include/xmloff/xmlnumfe.hxx | 2 include/xmloff/xmltoken.hxx | 1 sc/qa/unit/data/ods/tdf156449-Blank-In-Exponent.ods |binary sc/qa/unit/subsequent_export_test4.cxx | 31 ++++++++++++ schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng | 9 +++ svl/qa/unit/svl.cxx | 8 +++ svl/source/numbers/zformat.cxx | 8 +-- xmloff/source/core/xmltoken.cxx | 1 xmloff/source/style/xmlnumfe.cxx | 20 ++++++- xmloff/source/style/xmlnumfi.cxx | 16 +++++- xmloff/source/token/tokens.txt | 1 12 files changed, 89 insertions(+), 10 deletions(-)
New commits: commit 443027cd71aef3eb45bbf3631869ea63457f2c81 Author: Laurent Balland <laurent.ball...@mailo.fr> AuthorDate: Wed Jul 26 17:58:00 2023 +0200 Commit: Laurent Balland <laurent.ball...@mailo.fr> CommitDate: Wed Nov 1 11:43:54 2023 +0100 tdf#156449 Preserve '0' or '?' in exponent Exponent in scientific number may use '?' as blank like in format "0.00E+?0" This change: - adds interpreatation of '0' and '?' in exponent - adds "blank-exponent-digits" attribute to scientific number for import and export to ODF - prevents using exponent with only '?'. There must be at least one '0' in exponent - adds QA test of such format and test import/export/import to ODF and OOXML - corrects one basic test Change-Id: If52edc632a161f842270bb2fd77af535e2b978d4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154986 Tested-by: Jenkins Reviewed-by: Laurent Balland <laurent.ball...@mailo.fr> diff --git a/basic/qa/vba_tests/format.vb b/basic/qa/vba_tests/format.vb index 0e997ca824db..4e62e87e498e 100644 --- a/basic/qa/vba_tests/format.vb +++ b/basic/qa/vba_tests/format.vb @@ -135,7 +135,7 @@ Sub Custom_Number_Format_Sample() TestUtil.AssertEqual(Format(12345.25, "#,###.##"), "12,345.25", "Format(12345.25, ""#,###.##"")") TestUtil.AssertEqual(Format(0.25, "##.00%"), "25.00%", "Format(0.25, ""##.00%"")") TestUtil.AssertEqual(Format(1000000, "#,###"), "1,000,000", "Format(1000000, ""#,###"")") - TestUtil.AssertEqual(Format(1.09837555, "#.#####E+###"), "1.09838E+000", "Format(1.09837555, ""#.#####E+###"")") + TestUtil.AssertEqual(Format(1.09837555, "#.#####E+000"), "1.09838E+000", "Format(1.09837555, ""#.#####E+000"")") TestUtil.AssertEqual(Format(1.09837555, "###.####E#"), "1.0984E0", "Format(1.09837555, ""###.####E#"")") TestUtil.AssertEqual(Format(1098.37555, "###.####E#"), "1.0984E3", "Format(1098.37555, ""###.####E#"")") TestUtil.AssertEqual(Format(1098375.55, "###.####E#"), "1.0984E6", "Format(1098375.55, ""###.####E#"")") diff --git a/include/xmloff/xmlnumfe.hxx b/include/xmloff/xmlnumfe.hxx index 8421c5f7e323..d96f40c53a04 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 bExponentLowercase, + bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign, bool bExponentLowercase, sal_Int32 nBlankExp, 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 62f3ebcd613d..4e6441841774 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -3469,6 +3469,7 @@ namespace xmloff::token { XML_EXPONENT_INTERVAL, XML_EXPONENT_LOWERCASE, XML_FORCED_EXPONENT_SIGN, + XML_BLANK_EXPONENT_DIGITS, XML_MIN_DECIMAL_PLACES, XML_MAX_DENOMINATOR_VALUE, XML_MAX_NUMERATOR_DIGITS, diff --git a/sc/qa/unit/data/ods/tdf156449-Blank-In-Exponent.ods b/sc/qa/unit/data/ods/tdf156449-Blank-In-Exponent.ods new file mode 100644 index 000000000000..73647881e73d Binary files /dev/null and b/sc/qa/unit/data/ods/tdf156449-Blank-In-Exponent.ods differ diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx index 2b4141425136..390783131733 100644 --- a/sc/qa/unit/subsequent_export_test4.cxx +++ b/sc/qa/unit/subsequent_export_test4.cxx @@ -1468,6 +1468,37 @@ void lcl_TestNumberFormat(ScDocument& rDoc, const OUString& rFormatStrOK) CPPUNIT_ASSERT_EQUAL(rFormatStrOK, rFormatStr); } + +void lcl_SetNumberFormat(ScDocument& rDoc, const OUString& rFormat) +{ + sal_Int32 nCheckPos; + SvNumFormatType nType; + sal_uInt32 nFormat; + OUString aNewFormat = rFormat; + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + if (pFormatter) + { + pFormatter->PutEntry(aNewFormat, nCheckPos, nType, nFormat); + rDoc.SetNumberFormat(ScAddress(0, 0, 0), nFormat); + } +} +} + +CPPUNIT_TEST_FIXTURE(ScExportTest4, testBlankInExponent) +{ + createScDoc("ods/tdf156449-Blank-In-Exponent.ods"); + + // save to ODS and reload + saveAndReload("calc8"); + lcl_TestNumberFormat(*getScDoc(), "0.00E+?0"); + lcl_SetNumberFormat(*getScDoc(), "0.00E+??"); + // at least one '0' in exponent + saveAndReload("calc8"); + lcl_TestNumberFormat(*getScDoc(), "0.00E+?0"); + + // save to XLSX and reload + saveAndReload("Calc Office Open XML"); + lcl_TestNumberFormat(*getScDoc(), "0.00E+?0"); } CPPUNIT_TEST_FIXTURE(ScExportTest4, testSecondsWithoutTruncateAndDecimals) diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index c9993768aa40..aeb4c77ea40e 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -2854,6 +2854,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. </rng:optional> </rng:define> + <!-- TODO no proposal, --> + <rng:define name="number-scientific-number-attlist" combine="interleave"> + <rng:optional> + <rng:attribute name="loext:blank-exponent-digits"> + <rng:ref name="positiveInteger"/> + </rng:attribute> + </rng:optional> + </rng:define> + <!-- TODO no proposal --> <rng:define name="table-data-pilot-level-attlist" combine="interleave"> <rng:optional> diff --git a/svl/qa/unit/svl.cxx b/svl/qa/unit/svl.cxx index f43cfb9e437e..4fa56f4bccd4 100644 --- a/svl/qa/unit/svl.cxx +++ b/svl/qa/unit/svl.cxx @@ -1743,6 +1743,14 @@ void Test::testUserDefinedNumberFormats() sExpected = "271433.605"; checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); } + { // tdf#156449 Use '?' in exponent of scientific number + sCode = "0.00E+?0"; + sExpected = "3.14E+ 0"; // before change it was "3.14E+00" + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + // There should be at least one '0' in exponent + sCode = "0.00E+??"; + checkPreviewString(aFormatter, sCode, M_PI, eLang, sExpected); + } { // tdf#33689 use English NfKeywords in non-English language eLang = LANGUAGE_DUTCH; sExpected = "Dutch: 1900/01/02 03:23:53"; diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx index 174ab1c15667..b5c8757ef2e6 100644 --- a/svl/source/numbers/zformat.cxx +++ b/svl/source/numbers/zformat.cxx @@ -2752,11 +2752,10 @@ bool SvNumberformat::ImpGetScientificOutput(double fNumber, } sal_uInt16 j = nCnt-1; // Last symbol - sal_Int32 k; // Position in ExpStr + sal_Int32 k = ExpStr.getLength() - 1; // Position in ExpStr sal_Int32 nZeros = 0; // Erase leading zeros - bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP); - + // erase all leading zeros except last one while (nZeros < k && ExpStr[nZeros] == '0') { ++nZeros; @@ -2766,6 +2765,9 @@ bool SvNumberformat::ImpGetScientificOutput(double fNumber, ExpStr.remove( 0, nZeros); } + // restore leading zeros or blanks according to format '0' or '?' tdf#156449 + bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP); + bool bCont = true; if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP) diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 00234904e7b4..6879f37db295 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -3474,6 +3474,7 @@ namespace xmloff::token { TOKEN( "exponent-interval", XML_EXPONENT_INTERVAL ), TOKEN( "exponent-lowercase", XML_EXPONENT_LOWERCASE ), TOKEN( "forced-exponent-sign", XML_FORCED_EXPONENT_SIGN ), + TOKEN( "blank-exponent-digits", XML_BLANK_EXPONENT_DIGITS ), TOKEN( "min-decimal-places", XML_MIN_DECIMAL_PLACES ), TOKEN( "max-denominator-value", XML_MAX_DENOMINATOR_VALUE ), TOKEN( "max-numerator-digits", XML_MAX_NUMERATOR_DIGITS ), diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx index ee09dd0b39d8..406c22236a71 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 bExponentLowercase, + bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign, bool bExponentLowercase, sal_Int32 nBlankExp, const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries ) { FinishTextElement_Impl(); @@ -759,6 +759,12 @@ void SvXMLNumFmtExport::WriteScientificElement_Impl( { if (bExponentLowercase) m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_EXPONENT_LOWERCASE, XML_TRUE ); + if (nBlankExp > 0) + { + if (nBlankExp >= nExp) + nBlankExp = nExp - 1; // preserve at least one '0' in exponent + m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_EXPONENT_DIGITS, OUString::number( nBlankExp ) ); + } } SvXMLElementExport aElem( m_rExport, @@ -1360,7 +1366,8 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt bool bExpSign = true; bool bExponentLowercase = false; // 'e' or 'E' for scientific notation bool bDecAlign = false; // decimal alignment with "?" - sal_Int32 nExpDigits = 0; + sal_Int32 nExpDigits = 0; // '0' and '?' in exponent + sal_Int32 nBlankExp = 0; // only '?' in exponent sal_Int32 nIntegerSymbols = 0; // for embedded-text, including "#" sal_Int32 nTrailingThousands = 0; // thousands-separators after all digits sal_Int32 nMinDecimals = nPrecision; @@ -1383,7 +1390,14 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt break; case NF_SYMBOLTYPE_DIGIT: if ( bExpFound && pElemStr ) + { nExpDigits += pElemStr->getLength(); + for ( sal_Int32 i = pElemStr->getLength()-1; i >= 0 ; i-- ) + { + if ( (*pElemStr)[i] == '?' ) + nBlankExp ++; + } + } else if ( !bDecDashes && pElemStr && (*pElemStr)[0] == '-' ) { bDecDashes = true; @@ -1682,7 +1696,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, - bExponentLowercase, aEmbeddedEntries ); + bExponentLowercase, nBlankExp, aEmbeddedEntries ); bAnyContent = true; break; case SvNumFormatType::FRACTION: diff --git a/xmloff/source/style/xmlnumfi.cxx b/xmloff/source/style/xmlnumfi.cxx index d72914937eb3..f6d05e94c1bf 100644 --- a/xmloff/source/style/xmlnumfi.cxx +++ b/xmloff/source/style/xmlnumfi.cxx @@ -90,7 +90,8 @@ struct SvXMLNumberInfo sal_Int32 nDecimals = -1; sal_Int32 nInteger = -1; /// Total min number of digits in integer part ('0' + '?') sal_Int32 nBlankInteger = -1; /// Number of '?' in integer part - sal_Int32 nExpDigits = -1; + sal_Int32 nExpDigits = -1; /// Number of '0' and '?' in exponent + sal_Int32 nBlankExp = -1; /// Number of '?' in exponent sal_Int32 nExpInterval = -1; sal_Int32 nMinNumerDigits = -1; sal_Int32 nMinDenomDigits = -1; @@ -713,6 +714,11 @@ SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport, if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nExpDigits = std::min<sal_Int32>(nAttrVal, NF_MAX_FORMAT_SYMBOLS); break; + case XML_ELEMENT(NUMBER, XML_BLANK_EXPONENT_DIGITS): + case XML_ELEMENT(LO_EXT, XML_BLANK_EXPONENT_DIGITS): + if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) + aNumInfo.nBlankExp = std::min<sal_Int32>(nAttrVal, NF_MAX_FORMAT_SYMBOLS); + break; case XML_ELEMENT(NUMBER, XML_EXPONENT_INTERVAL): case XML_ELEMENT(LO_EXT, XML_EXPONENT_INTERVAL): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) @@ -808,6 +814,9 @@ SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport, else aNumInfo.nMinDecimalDigits = aNumInfo.nDecimals; } + if ( aNumInfo.nExpDigits > 0 && aNumInfo.nBlankExp >= aNumInfo.nExpDigits ) + aNumInfo.nBlankExp = aNumInfo.nExpDigits - 1; // at least one '0' in exponent + if ( aNumInfo.nZerosDenomDigits > 0 ) { // nMin = count of '0' and '?' if ( aNumInfo.nMinDenomDigits < aNumInfo.nZerosDenomDigits ) @@ -1878,7 +1887,10 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo ) } for (sal_Int32 i=0; i<rInfo.nExpDigits; i++) { - aNumStr.append( '0' ); + if ( i < rInfo.nBlankExp ) + aNumStr.append( '?' ); + else + aNumStr.append( '0' ); } } diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index cdd387702531..7eb09f6f2f9f 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -3230,6 +3230,7 @@ external-data exponent-interval exponent-lowercase forced-exponent-sign +blank-exponent-digits min-decimal-places max-denominator-value max-numerator-digits