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.balland@mailo.fr>
diff --git a/basic/qa/vba_tests/format.vb b/basic/qa/vba_tests/format.vb
index 0e997ca..4e62e87 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 8421c5f..d96f40c 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 62f3ebc..4e644184 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 0000000..7364788
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf156449-Blank-In-Exponent.ods
Binary files differ
diff --git a/sc/qa/unit/subsequent_export_test4.cxx b/sc/qa/unit/subsequent_export_test4.cxx
index 2b414142..3907831 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 c999376..aeb4c77 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 f43cfb9..4fa56f4 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 174ab1c..b5c8757 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 00234904..6879f37 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 ee09dd0..406c2223 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 d729149..f6d05e9 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 cdd3877..7eb09f6 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