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)