tdf#96723 Number format: embedded text in decimal

Embedded text in decimal part is represented by negative position
Use number:position as it is defined as integer in schema [1]
Add Unit test to import XLSX file with embedded text in decimal
  and export to ODS

[1] https://opengrok.libreoffice.org/xref/core/schema/odf1.3/OpenDocument-v1.3-schema.rng?r=7f3c9da5#7142

Change-Id: Ic68471a071ccbb1c3bec442bfcbe21d84f41ebd8
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135918
Tested-by: Jenkins
Reviewed-by: Eike Rathke <erack@redhat.com>
(cherry picked from commit 56dff7b244fb0ef28951193a410dd5c4a3126590)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136986
Reviewed-by: Laurent Balland <laurent.balland@mailo.fr>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/sc/qa/unit/data/xlsx/embedded-text-in-decimal.xlsx b/sc/qa/unit/data/xlsx/embedded-text-in-decimal.xlsx
new file mode 100644
index 0000000..77b2d02
--- /dev/null
+++ b/sc/qa/unit/data/xlsx/embedded-text-in-decimal.xlsx
Binary files differ
diff --git a/sc/qa/unit/subsequent_export_test2.cxx b/sc/qa/unit/subsequent_export_test2.cxx
index 6cdb985..a49cb97 100644
--- a/sc/qa/unit/subsequent_export_test2.cxx
+++ b/sc/qa/unit/subsequent_export_test2.cxx
@@ -190,6 +190,7 @@ public:
    void testXlsxRowsOrder();
    void testTdf91286();
    void testTdf148820();
    void testEmbeddedTextInDecimal();

    CPPUNIT_TEST_SUITE(ScExportTest2);

@@ -312,6 +313,7 @@ public:
    CPPUNIT_TEST(testXlsxRowsOrder);
    CPPUNIT_TEST(testTdf91286);
    CPPUNIT_TEST(testTdf148820);
    CPPUNIT_TEST(testEmbeddedTextInDecimal);

    CPPUNIT_TEST_SUITE_END();

@@ -3094,6 +3096,32 @@ void ScExportTest2::testTdf148820()
    xDocSh->DoClose();
}

namespace
{
void lcl_TestEmbeddedTextInDecimal(ScDocShellRef xDocSh)
{
    CPPUNIT_ASSERT(xDocSh);
    ScDocument& rDoc = xDocSh->GetDocument();
    sal_uInt32 nNumberFormat = rDoc.GetNumberFormat(0, 0, 0);
    const SvNumberformat* pNumberFormat = rDoc.GetFormatTable()->GetEntry(nNumberFormat);
    const OUString& rFormatStr = pNumberFormat->GetFormatstring();

    CPPUNIT_ASSERT_EQUAL(OUString("#,##0.000\" \"###\" \"###"), rFormatStr);
}
}

void ScExportTest2::testEmbeddedTextInDecimal()
{
    ScDocShellRef xDocSh = loadDoc(u"embedded-text-in-decimal.", FORMAT_XLSX);
    lcl_TestEmbeddedTextInDecimal(xDocSh);

    // save to ODS and reload
    xDocSh = saveAndReload(*xDocSh, FORMAT_ODS);
    lcl_TestEmbeddedTextInDecimal(xDocSh);

    xDocSh->DoClose();
}

CPPUNIT_TEST_SUITE_REGISTRATION(ScExportTest2);

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx
index d928f8b..b411494 100644
--- a/xmloff/source/style/xmlnumfe.cxx
+++ b/xmloff/source/style/xmlnumfe.cxx
@@ -602,6 +602,8 @@ void SvXMLNumFmtExport::WriteNumberElement_Impl(
        const SvXMLEmbeddedTextEntry *const pObj = &rEmbeddedEntries[nEntry];

        //  position attribute
        // position == 0 is between first integer digit and decimal separator
        // position < 0 is inside decimal part
        rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_POSITION,
                                OUString::number( pObj->nFormatPos ) );
        SvXMLElementExport aChildElem( rExport, XML_NAMESPACE_NUMBER, XML_EMBEDDED_TEXT,
@@ -1388,6 +1390,10 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
        if ( bAllowEmbedded )
        {
            sal_Int32 nDigitsPassed = 0;
            sal_Int32 nEmbeddedPositionsMax = nIntegerSymbols;
            // Enable embedded text in decimal part only if there's a decimal part
            if ( nPrecision )
                nEmbeddedPositionsMax += nPrecision + 1;
            nPos = 0;
            bEnd = false;
            while (!bEnd)
@@ -1404,12 +1410,15 @@ void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt
                        if ( pElemStr )
                            nDigitsPassed += pElemStr->getLength();
                        break;
                    case NF_SYMBOLTYPE_DECSEP:
                        nDigitsPassed++;
                        break;
                    case NF_SYMBOLTYPE_STRING:
                    case NF_SYMBOLTYPE_BLANK:
                    case NF_SYMBOLTYPE_PERCENT:
                        if ( nDigitsPassed > 0 && nDigitsPassed < nIntegerSymbols && pElemStr )
                        if ( 0 < nDigitsPassed && nDigitsPassed < nEmbeddedPositionsMax && pElemStr )
                        {
                            //  text (literal or underscore) within the integer part of a number:number element
                            //  text (literal or underscore) within the integer (>=0) or decimal (<0) part of a number:number element

                            OUString aEmbeddedStr;
                            if ( nElemType == NF_SYMBOLTYPE_STRING || nElemType == NF_SYMBOLTYPE_PERCENT )
diff --git a/xmloff/source/style/xmlnumfi.cxx b/xmloff/source/style/xmlnumfi.cxx
index 46ee44b..b046e5f 100644
--- a/xmloff/source/style/xmlnumfi.cxx
+++ b/xmloff/source/style/xmlnumfi.cxx
@@ -456,7 +456,7 @@ SvXMLNumFmtEmbeddedTextContext::SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rIm
    {
        if ( aIter.getToken() == XML_ELEMENT(NUMBER, XML_POSITION) )
        {
            if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 ))
            if (::sax::Converter::convertNumber( nAttrVal, aIter.toView() ))
                nTextPosition = nAttrVal;
        }
        else
@@ -1706,8 +1706,8 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )

    bool bGrouping = rInfo.bGrouping;
    size_t const nEmbeddedCount = rInfo.m_EmbeddedElements.size();
    if ( nEmbeddedCount )
        bGrouping = false;      // grouping and embedded characters can't be used together
    if ( nEmbeddedCount && rInfo.m_EmbeddedElements.rbegin()->first > 0 )
        bGrouping = false;      // grouping and embedded characters in integer part can't be used together

    sal_uInt32 nStdIndex = pFormatter->GetStandardIndex( nFormatLang );
    OUStringBuffer aNumStr(pFormatter->GenerateFormat( nStdIndex, nFormatLang,
@@ -1745,10 +1745,21 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
        }
    }

    if ( ( rInfo.bDecReplace || rInfo.nMinDecimalDigits < rInfo.nDecimals ) && nPrec )     // add decimal replacement (dashes)
    {
        //  add dashes for explicit decimal replacement, # or ? for variable decimals
        sal_Unicode cAdd = rInfo.bDecReplace ? '-' : ( rInfo.bDecAlign ? '?': '#' );

        if ( rInfo.nMinDecimalDigits == 0 )
            aNumStr.append( pData->GetLocaleData( nFormatLang ).getNumDecimalSep() );
        for ( sal_uInt16 i=rInfo.nMinDecimalDigits; i<nPrec; i++)
            aNumStr.append( cAdd );
    }

    if ( nEmbeddedCount )
    {
        //  insert embedded strings into number string
        //  only the integer part is supported
        //  support integer (position >=0) and decimal (position <0) part
        //  nZeroPos is the string position where format position 0 is inserted

        sal_Int32 nZeroPos = aNumStr.indexOf( pData->GetLocaleData( nFormatLang ).getNumDecimalSep() );
@@ -1777,7 +1788,7 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )
        {
            sal_Int32 const nFormatPos = it.first;
            sal_Int32 nInsertPos = nZeroPos - nFormatPos;
            if ( nFormatPos >= 0 && nInsertPos >= 0 )
            if ( nInsertPos >= 0 )
            {
                //  #107805# always quote embedded strings - even space would otherwise
                //  be recognized as thousands separator in French.
@@ -1791,17 +1802,6 @@ void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo )

    aFormatCode.append( aNumStr );

    if ( ( rInfo.bDecReplace || rInfo.nMinDecimalDigits < rInfo.nDecimals ) && nPrec )     // add decimal replacement (dashes)
    {
        //  add dashes for explicit decimal replacement, # or ? for variable decimals
        sal_Unicode cAdd = rInfo.bDecReplace ? '-' : ( rInfo.bDecAlign ? '?': '#' );

        if ( rInfo.nMinDecimalDigits == 0 )
            aFormatCode.append( pData->GetLocaleData( nFormatLang ).getNumDecimalSep() );
        for ( sal_uInt16 i=rInfo.nMinDecimalDigits; i<nPrec; i++)
            aFormatCode.append( cAdd );
    }

    //  add extra thousands separators for display factor

    if (rInfo.fDisplayFactor == 1.0 || rInfo.fDisplayFactor <= 0.0)