include/xmloff/xmlnumfe.hxx                                 |    1 
 include/xmloff/xmltoken.hxx                                 |    1 
 sc/qa/unit/data/ods/tdf152724-Blank-width-char.ods          |binary
 sc/qa/unit/subsequent_export_test4.cxx                      |   13 +
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |   28 +++
 xmloff/source/core/xmltoken.cxx                             |    1 
 xmloff/source/style/xmlnumfe.cxx                            |  107 +++++++++---
 xmloff/source/style/xmlnumfi.cxx                            |  103 ++++++++++-
 xmloff/source/token/tokens.txt                              |    1 
 9 files changed, 222 insertions(+), 33 deletions(-)

New commits:
commit 74d9da037cac01c5abd768a99b2f948553fbf144
Author:     Laurent Balland <laurent.ball...@mailo.fr>
AuthorDate: Sat Feb 4 09:29:20 2023 +0100
Commit:     Eike Rathke <er...@redhat.com>
CommitDate: Fri Aug 25 17:49:17 2023 +0200

    tdf#152724 Extend ODF for blank width "_x"
    
    Number format code "_x" is currently saved as a text string containing a
    number of spaces corresponding to the width of character "x". It may be
    confusing for user if its format code is modified.
    This change introduces a new XML tag XML_BLANK_WIDTH_CHAR to replace the
    previous text string if ODF version is extended
    
    <number:text> and <number:embedded-text>:
    the attribute is composed of a string containing the used character and its
    position in the text string (if position is 0, it is omitted).
    Several blank code characters are separated by '_'.
    Replacement blanks in the text string are preserved to enable compatibility.
    
    Example: format code
    "foo"_M_I_N"!"???,???.000_.000"!"_)
    is saved as:
      <number:number-style style:name="N173">
       <number:text loext:blank-width-char="M3_I6_N7">foo       !</number:text>
       <number:number number:decimal-places="6" number:min-decimal-places="6" 
number:min-integer-digits="6" loext:max-blank-integer-digits="6" 
number:grouping="true">
        <number:embedded-text number:position="-4" loext:blank-width-char="."> 
</number:embedded-text>
       </number:number>
       <number:text loext:blank-width-char=")1">! </number:text>
      </number:number-style>
    
    Add QA test
    
    Change-Id: I785e1a14ecccc900e9fd5af88dd7b743fefcc48c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146582
    Tested-by: Jenkins
    Reviewed-by: Eike Rathke <er...@redhat.com>

diff --git a/include/xmloff/xmlnumfe.hxx b/include/xmloff/xmlnumfe.hxx
index 43e1814dc26b..7c76e5117b1f 100644
--- a/include/xmloff/xmlnumfe.hxx
+++ b/include/xmloff/xmlnumfe.hxx
@@ -51,6 +51,7 @@ private:
     OUString                    m_sPrefix;
     SvNumberFormatter*          m_pFormatter;
     OUStringBuffer              m_sTextContent;
+    OUStringBuffer              m_sBlankWidthString;
     bool                        m_bHasText;
     std::unique_ptr<SvXMLNumUsedList_Impl>      m_pUsedList;
     std::unique_ptr<LocaleDataWrapper>          m_pLocaleData;
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index e8ffd3fedcd5..7771577a453d 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -3466,6 +3466,7 @@ namespace xmloff::token {
         XML_ZEROS_DENOMINATOR_DIGITS,
         XML_INTEGER_FRACTION_DELIMITER,
         XML_MAX_BLANK_INTEGER_DIGITS,
+        XML_BLANK_WIDTH_CHAR,
 
         // tdf#115319
         XML_REFERENCE_LANGUAGE,
diff --git a/sc/qa/unit/data/ods/tdf152724-Blank-width-char.ods 
b/sc/qa/unit/data/ods/tdf152724-Blank-width-char.ods
new file mode 100644
index 000000000000..4716d4be5355
Binary files /dev/null and b/sc/qa/unit/data/ods/tdf152724-Blank-width-char.ods 
differ
diff --git a/sc/qa/unit/subsequent_export_test4.cxx 
b/sc/qa/unit/subsequent_export_test4.cxx
index 74313ef67c27..357ac567575f 100644
--- a/sc/qa/unit/subsequent_export_test4.cxx
+++ b/sc/qa/unit/subsequent_export_test4.cxx
@@ -1480,6 +1480,19 @@ CPPUNIT_TEST_FIXTURE(ScExportTest4, 
testSecondsWithoutTruncateAndDecimals)
     lcl_TestNumberFormat(*getScDoc(), "[SS].00");
 }
 
+CPPUNIT_TEST_FIXTURE(ScExportTest4, testBlankWidthCharacter)
+{
+    createScDoc("ods/tdf152724-Blank-width-char.ods");
+
+    // save to ODS and reload
+    saveAndReload("calc8");
+    lcl_TestNumberFormat(*getScDoc(), "[>0]_-?0;[<0]-?0;_-?0;@");
+
+    // save to XLSX and reload
+    saveAndReload("Calc Office Open XML");
+    lcl_TestNumberFormat(*getScDoc(), "_-?0;-?0;_-?0;@");
+}
+
 CPPUNIT_TEST_FIXTURE(ScExportTest4, testEmbeddedTextInDecimal)
 {
     createScDoc("xlsx/embedded-text-in-decimal.xlsx");
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index b1a344e7bf4a..b7ad4b8a1a7c 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2047,6 +2047,14 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
         <rng:empty/>
       </rng:element>
     </rng:define>
+    <rng:define name="number-text">
+      <rng:element name="number:text">
+        <rng:optional>
+          <rng:ref name="number-text-attlist"/>
+         </rng:optional>
+        <rng:text/>
+      </rng:element>
+    </rng:define>
 
    </rng:include>
 
@@ -2745,6 +2753,26 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:optional>
   </rng:define>
 
+  <rng:define name="number-embedded-text-attlist" combine="interleave">
+    <!-- TODO no proposal,  -->
+    <rng:optional>
+      <rng:attribute name="loext:blank-width-char">
+        <rng:ref name="string"/>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
+  <!-- TODO no proposal,  -->
+  <rng:define name="number-text-attlist">
+    <rng:interleave>
+      <rng:optional>
+        <rng:attribute name="loext:blank-width-char">
+          <rng:ref name="string"/>
+        </rng:attribute>
+      </rng:optional>
+    </rng:interleave>
+  </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 8127996aa1c9..4f6f2223e721 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -3471,6 +3471,7 @@ namespace xmloff::token {
         TOKEN( "zeros-denominator-digits",        XML_ZEROS_DENOMINATOR_DIGITS 
),
         TOKEN( "integer-fraction-delimiter",      
XML_INTEGER_FRACTION_DELIMITER ),
         TOKEN( "max-blank-integer-digits",        XML_MAX_BLANK_INTEGER_DIGITS 
),
+        TOKEN( "blank-width-char",                XML_BLANK_WIDTH_CHAR ),
 
         // for optional language-dependent reference formats
         TOKEN( "reference-language",              XML_REFERENCE_LANGUAGE ),
diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx
index fb767dc3a10d..67675cf22ab3 100644
--- a/xmloff/source/style/xmlnumfe.cxx
+++ b/xmloff/source/style/xmlnumfe.cxx
@@ -66,9 +66,10 @@ struct SvXMLEmbeddedTextEntry
     sal_uInt16      nSourcePos;     // position in NumberFormat (to skip later)
     sal_Int32       nFormatPos;     // resulting position in embedded-text 
element
     OUString   aText;
+    bool            isBlankWidth;   // "_x"
 
-    SvXMLEmbeddedTextEntry( sal_uInt16 nSP, sal_Int32 nFP, OUString aT ) :
-        nSourcePos(nSP), nFormatPos(nFP), aText(std::move(aT)) {}
+    SvXMLEmbeddedTextEntry( sal_uInt16 nSP, sal_Int32 nFP, OUString aT, bool 
bBW = false ) :
+        nSourcePos(nSP), nFormatPos(nFP), aText(std::move(aT)), isBlankWidth( 
bBW ) {}
 };
 
 }
@@ -323,6 +324,16 @@ void SvXMLNumFmtExport::FinishTextElement_Impl(bool 
bUseExtensionNS)
 {
     if ( m_bHasText )
     {
+        if ( !m_sBlankWidthString.isEmpty() )
+        {
+            // Export only for 1.3 with extensions and later.
+            SvtSaveOptions::ODFSaneDefaultVersion eVersion = 
m_rExport.getSaneDefaultVersion();
+            if (eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & 
SvtSaveOptions::ODFSVER_EXTENDED) != 0 ))
+            {
+                m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, 
XML_BLANK_WIDTH_CHAR,
+                                      m_sBlankWidthString.makeStringAndClear() 
);
+            }
+        }
         sal_uInt16 nNS = bUseExtensionNS ? XML_NAMESPACE_LO_EXT : 
XML_NAMESPACE_NUMBER;
         SvXMLElementExport aElem( m_rExport, nNS, XML_TEXT,
                                   true, false );
@@ -502,6 +513,36 @@ void SvXMLNumFmtExport::WriteRepeatedElement_Impl( 
sal_Unicode nChar )
     }
 }
 
+namespace {
+void lcl_WriteBlankWidthString( std::u16string_view rBlankWidthChar, 
OUStringBuffer& rBlankWidthString, OUStringBuffer& rTextContent )
+{
+    // export "_x"
+    if ( rBlankWidthString.isEmpty() )
+    {
+        rBlankWidthString.append( rBlankWidthChar );
+        if ( !rTextContent.isEmpty() )
+        {
+            // add position in rTextContent
+            rBlankWidthString.append( rTextContent.getLength() );
+        }
+    }
+    else
+    {
+        // add "_" as separator if there are several blank width char
+        rBlankWidthString.append( "_" );
+        rBlankWidthString.append( rBlankWidthChar );
+        rBlankWidthString.append( rTextContent.getLength() );
+    }
+    // for previous versions, turn "_x" into the number of spaces used for x 
in InsertBlanks in the NumberFormat
+    if ( !rBlankWidthChar.empty() )
+    {
+        OUString aBlanks;
+        SvNumberformat::InsertBlanks( aBlanks, 0, rBlankWidthChar[0] );
+        rTextContent.append( aBlanks );
+    }
+}
+}
+
 void SvXMLNumFmtExport::WriteSecondsElement_Impl( bool bLong, sal_uInt16 
nDecimals )
 {
     FinishTextElement_Impl();
@@ -553,28 +594,45 @@ void SvXMLNumFmtExport::WriteIntegerElement_Impl(
 void SvXMLNumFmtExport::WriteEmbeddedEntries_Impl( const 
SvXMLEmbeddedTextEntryArr& rEmbeddedEntries )
 {
     auto nEntryCount = rEmbeddedEntries.size();
+    SvtSaveOptions::ODFSaneDefaultVersion eVersion = 
m_rExport.getSaneDefaultVersion();
     for (decltype(nEntryCount) nEntry=0; nEntry < nEntryCount; ++nEntry)
     {
-        const SvXMLEmbeddedTextEntry *const pObj = &rEmbeddedEntries[nEntry];
+        const SvXMLEmbeddedTextEntry* pObj = &rEmbeddedEntries[nEntry];
 
         //  position attribute
         // position == 0 is between first integer digit and decimal separator
         // position < 0 is inside decimal part
         m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_POSITION,
                                 OUString::number( pObj->nFormatPos ) );
-        SvXMLElementExport aChildElem( m_rExport, XML_NAMESPACE_NUMBER, 
XML_EMBEDDED_TEXT,
-                                          true, false );
 
         //  text as element content
-        OUStringBuffer aContent( pObj->aText );
-        while ( nEntry+1 < nEntryCount && 
rEmbeddedEntries[nEntry+1].nFormatPos == pObj->nFormatPos )
+        OUStringBuffer aContent;
+        OUStringBuffer aBlankWidthString;
+        do
         {
-            // The array can contain several elements for the same position in 
the number
-            // (for example, literal text and space from underscores). They 
must be merged
-            // into a single embedded-text element.
-            aContent.append(rEmbeddedEntries[nEntry+1].aText);
+            pObj = &rEmbeddedEntries[nEntry];
+            if ( pObj->isBlankWidth  )
+            {
+                //  (#i20396# the spaces may also be in embedded-text elements)
+                lcl_WriteBlankWidthString( pObj->aText, aBlankWidthString, 
aContent );
+            }
+            else
+            {
+                // The array can contain several elements for the same 
position in the number.
+                // Literal texts are merged into a single embedded-text 
element.
+                aContent.append( pObj->aText );
+            }
             ++nEntry;
         }
+        while ( nEntry < nEntryCount
+            && rEmbeddedEntries[nEntry].nFormatPos == pObj->nFormatPos );
+        --nEntry;
+
+        // Export only for 1.3 with extensions and later.
+        if ( !aBlankWidthString.isEmpty() && eVersion > 
SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) 
!= 0 ) )
+            m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, 
XML_BLANK_WIDTH_CHAR, aBlankWidthString.makeStringAndClear() );
+        SvXMLElementExport aChildElem( m_rExport, XML_NAMESPACE_NUMBER, 
XML_EMBEDDED_TEXT,
+                                          true, false );
         m_rExport.Characters( aContent.makeStringAndClear() );
     }
 }
@@ -1196,13 +1254,13 @@ void SvXMLNumFmtExport::ExportPart_Impl( const 
SvNumberformat& rFormat, sal_uInt
                               aAttr.Style );
     }
 
+    SvtSaveOptions::ODFSaneDefaultVersion eVersion = 
m_rExport.getSaneDefaultVersion();
     if ( !aAttr.Spellout.isEmpty() )
     {
         const bool bWriteSpellout = aAttr.Format.isEmpty();
         assert(bWriteSpellout);     // mutually exclusive
 
         // Export only for 1.2 and later with extensions
-        SvtSaveOptions::ODFSaneDefaultVersion eVersion = 
m_rExport.getSaneDefaultVersion();
         // Also ensure that duplicated transliteration-language and
         // transliteration-country attributes never escape into the wild with
         // releases.
@@ -1389,7 +1447,6 @@ void SvXMLNumFmtExport::ExportPart_Impl( const 
SvNumberformat& rFormat, sal_uInt
         }
 
         //  collect strings for embedded-text (must be known before number 
element is written)
-        SvtSaveOptions::ODFSaneDefaultVersion eVersion = 
m_rExport.getSaneDefaultVersion();
         bool bAllowEmbedded = ( nFmtType == SvNumFormatType::ALL || nFmtType 
== SvNumFormatType::NUMBER ||
                                         nFmtType == SvNumFormatType::CURRENCY 
||
                                         // Export only for 1.x with extensions
@@ -1429,18 +1486,25 @@ void SvXMLNumFmtExport::ExportPart_Impl( const 
SvNumberformat& rFormat, sal_uInt
                             //  text (literal or underscore) within the 
integer (>=0) or decimal (<0) part of a number:number element
 
                             OUString aEmbeddedStr;
+                            bool bSaveBlankWidthSymbol = false;
                             if ( nElemType == NF_SYMBOLTYPE_STRING || 
nElemType == NF_SYMBOLTYPE_PERCENT )
                             {
                                 aEmbeddedStr = *pElemStr;
                             }
                             else if (pElemStr->getLength() >= 2)
                             {
-                                SvNumberformat::InsertBlanks( aEmbeddedStr, 0, 
(*pElemStr)[1] );
+                                if ( eVersion > SvtSaveOptions::ODFSVER_013 && 
( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) )
+                                {
+                                    aEmbeddedStr = pElemStr->copy( 1, 1 );
+                                    bSaveBlankWidthSymbol = true;
+                                }
+                                else //  turn "_x" into the number of spaces 
used for x in InsertBlanks in the NumberFormat
+                                    SvNumberformat::InsertBlanks( 
aEmbeddedStr, 0, (*pElemStr)[1] );
                             }
                             sal_Int32 nEmbedPos = nIntegerSymbols - 
nDigitsPassed;
 
                             aEmbeddedEntries.push_back(
-                                SvXMLEmbeddedTextEntry(nPos, nEmbedPos, 
aEmbeddedStr));
+                                SvXMLEmbeddedTextEntry( nPos, nEmbedPos, 
aEmbeddedStr, bSaveBlankWidthSymbol ));
                         }
                         break;
                 }
@@ -1505,13 +1569,12 @@ void SvXMLNumFmtExport::ExportPart_Impl( const 
SvNumberformat& rFormat, sal_uInt
                 case NF_SYMBOLTYPE_BLANK:
                     if ( pElemStr && !lcl_IsInEmbedded( aEmbeddedEntries, nPos 
) )
                     {
-                        //  turn "_x" into the number of spaces used for x in 
InsertBlanks in the NumberFormat
-                        //  (#i20396# the spaces may also be in embedded-text 
elements)
-
-                        OUString aBlanks;
-                        if (pElemStr->getLength() >= 2)
-                            SvNumberformat::InsertBlanks( aBlanks, 0, 
(*pElemStr)[1] );
-                        AddToTextElement_Impl( aBlanks );
+                        if ( pElemStr->getLength() == 2 )
+                        {
+                            OUString aBlankWidthChar = pElemStr->copy( 1 );
+                            lcl_WriteBlankWidthString( aBlankWidthChar, 
m_sBlankWidthString, m_sTextContent );
+                            m_bHasText = true;
+                        }
                     }
                     break;
                 case NF_KEY_GENERAL :
diff --git a/xmloff/source/style/xmlnumfi.cxx b/xmloff/source/style/xmlnumfi.cxx
index e43e732a4ba8..f9f9bce5f675 100644
--- a/xmloff/source/style/xmlnumfi.cxx
+++ b/xmloff/source/style/xmlnumfi.cxx
@@ -123,6 +123,7 @@ class SvXMLNumFmtElementContext : public SvXMLImportContext
     bool                    bLong;
     bool                    bTextual;
     OUString                sCalendar;
+    OUString                sBlankWidthString;
 
 public:
                 SvXMLNumFmtElementContext( SvXMLImport& rImport, sal_Int32 
nElement,
@@ -134,7 +135,7 @@ public:
     virtual void SAL_CALL characters( const OUString& rChars ) override;
     virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
 
-    void    AddEmbeddedElement( sal_Int32 nFormatPos, const OUString& rContent 
);
+    void    AddEmbeddedElement( sal_Int32 nFormatPos, std::u16string_view 
rContent, std::u16string_view rBlankWidthString );
 };
 
 class SvXMLNumFmtEmbeddedTextContext : public SvXMLImportContext
@@ -142,6 +143,7 @@ class SvXMLNumFmtEmbeddedTextContext : public 
SvXMLImportContext
     SvXMLNumFmtElementContext&  rParent;
     OUStringBuffer         aContent;
     sal_Int32                   nTextPosition;
+    OUString                    aBlankWidthString;
 
 public:
                 SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport, 
sal_Int32 nElement,
@@ -461,6 +463,11 @@ 
SvXMLNumFmtEmbeddedTextContext::SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rIm
             if (::sax::Converter::convertNumber( nAttrVal, aIter.toView() ))
                 nTextPosition = nAttrVal;
         }
+        else if ( aIter.getToken() == XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR)
+                || aIter.getToken() == XML_ELEMENT(NUMBER, 
XML_BLANK_WIDTH_CHAR) )
+        {
+            aBlankWidthString = aIter.toString();
+        }
         else
             XMLOFF_WARN_UNKNOWN("xmloff", aIter);
     }
@@ -473,7 +480,7 @@ void SvXMLNumFmtEmbeddedTextContext::characters( const 
OUString& rChars )
 
 void SvXMLNumFmtEmbeddedTextContext::endFastElement(sal_Int32 )
 {
-    rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear() );
+    rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear(), 
aBlankWidthString );
 }
 
 static bool lcl_ValidChar( sal_Unicode cChar, const SvXMLNumFormatContext& 
rParent )
@@ -778,6 +785,10 @@ SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( 
SvXMLImport& rImport,
             case XML_ELEMENT(NUMBER, XML_CALENDAR):
                 sCalendar = aIter.toString();
                 break;
+            case XML_ELEMENT(NUMBER, XML_BLANK_WIDTH_CHAR):
+            case XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR):
+                sBlankWidthString = aIter.toString();
+                break;
             default:
                 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
         }
@@ -856,15 +867,83 @@ void SvXMLNumFmtElementContext::characters( const 
OUString& rChars )
     aContent.append( rChars );
 }
 
-void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, 
const OUString& rContent )
+namespace {
+void lcl_InsertBlankWidthChars( std::u16string_view rBlankWidthString, 
OUStringBuffer& rContent )
 {
-    if (rContent.isEmpty())
-        return;
+    sal_Int32 nShiftPosition = 1; // rContent starts with a quote
+    const size_t nLenBlank = rBlankWidthString.size();
+    for ( size_t i = 0 ; i < nLenBlank ; i++ )
+    {
+        sal_Unicode nChar = rBlankWidthString[ i ];
+        OUString aBlanks;
+        SvNumberformat::InsertBlanks( aBlanks, 0, nChar );
+        sal_Int32 nPositionContent = 0;
+        if ( ++i < nLenBlank )
+        {
+            sal_Int32 nNext = rBlankWidthString.find( '_', i );
+            if ( static_cast<sal_Int32>( i ) < nNext )
+            {
+                nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i, 
nNext - i ) );
+                i = nNext;
+            }
+            else
+                nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i 
) );
+        }
+        nPositionContent += nShiftPosition;
+        if ( nPositionContent >= 0 )
+        {
+            rContent.remove( nPositionContent, aBlanks.getLength() );
+            if ( nPositionContent >= 1 && rContent[ nPositionContent-1 ] == 
'\"' )
+            {
+                nPositionContent--;
+                rContent.insert( nPositionContent, nChar );
+                rContent.insert( nPositionContent, '_' );
+            }
+            else
+            {
+                rContent.insert( nPositionContent, '\"' );
+                rContent.insert( nPositionContent, nChar );
+                rContent.insert( nPositionContent, "\"_" );
+                nShiftPosition += 2;
+            }
+            // rContent length was modified: remove blanks, add "_x"
+            nShiftPosition += 2 - aBlanks.getLength();
+        }
+    }
+    // remove empty string at the end of rContent
+    if ( std::u16string_view( rContent ).substr( rContent.getLength() - 2 ) == 
u"\"\"" )
+    {
+        sal_Int32 nLen = rContent.getLength();
+        if ( nLen >= 3 && rContent[ nLen-3 ] != '\\' )
+            rContent.truncate( nLen - 2 );
+    }
+}
+}
 
-    auto iterPair = aNumInfo.m_EmbeddedElements.emplace(nFormatPos, rContent);
+void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, 
std::u16string_view rContentEmbedded, std::u16string_view rBlankWidthString )
+{
+    if ( rContentEmbedded.empty() )
+        return;
+    OUStringBuffer aContentEmbedded( rContentEmbedded );
+    //  #107805# always quote embedded strings - even space would otherwise
+    //  be recognized as thousands separator in French.
+    aContentEmbedded.insert( 0, '"' );
+    aContentEmbedded.append( '"' );
+    if ( !rBlankWidthString.empty() )
+        lcl_InsertBlankWidthChars( rBlankWidthString, aContentEmbedded );
+
+    auto iterPair = aNumInfo.m_EmbeddedElements.emplace( nFormatPos, 
aContentEmbedded.toString() );
     if (!iterPair.second)
+    {
         // there's already an element at this position - append text to 
existing element
-        iterPair.first->second += rContent;
+        if ( iterPair.first->second.endsWith( "\"" ) && aContentEmbedded[ 0 ] 
== '"' )
+        {   // remove double quote
+            iterPair.first->second = OUString::Concat( 
iterPair.first->second.subView( 0, iterPair.first->second.getLength() - 1 ) )
+                                + aContentEmbedded.subView( 1, 
aContentEmbedded.getLength() - 1 );
+        }
+        else
+            iterPair.first->second += aContentEmbedded;
+    }
 }
 
 void SvXMLNumFmtElementContext::endFastElement(sal_Int32 )
@@ -889,6 +968,11 @@ void SvXMLNumFmtElementContext::endFastElement(sal_Int32 )
             if ( !aContent.isEmpty() )
             {
                 lcl_EnquoteIfNecessary( aContent, rParent );
+                if ( !sBlankWidthString.isEmpty() )
+                {
+                    lcl_InsertBlankWidthChars( sBlankWidthString, aContent );
+                    sBlankWidthString = "";
+                }
                 rParent.AddToCode( aContent );
                 aContent.setLength(0);
             }
@@ -1818,10 +1902,7 @@ void SvXMLNumFormatContext::AddNumber( const 
SvXMLNumberInfo& rInfo )
             sal_Int32 nInsertPos = nZeroPos - nFormatPos;
             if ( nInsertPos >= 0 )
             {
-                //  #107805# always quote embedded strings - even space would 
otherwise
-                //  be recognized as thousands separator in French.
-
-                aNumStr.insert(nInsertPos, OUString::Concat("\"") + it.second 
+ "\"");
+                aNumStr.insert( nInsertPos, it.second );
             }
         }
     }
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index e8022d14dfce..469d03645276 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -3227,6 +3227,7 @@ zeros-numerator-digits
 zeros-denominator-digits
 integer-fraction-delimiter
 max-blank-integer-digits
+blank-width-char
 reference-language
 newline
 creator-initials

Reply via email to