cui/source/tabpages/numpages.cxx                            |   41 ++-----
 editeng/source/items/numitem.cxx                            |   49 ++++++++
 include/editeng/numitem.hxx                                 |    4 
 include/xmloff/xmltoken.hxx                                 |    1 
 offapi/com/sun/star/style/NumberingLevel.idl                |   21 +++
 schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng |    9 +
 sd/source/ui/dlg/BulletAndPositionDlg.cxx                   |   18 +--
 svx/source/sidebar/nbdtmg.cxx                               |   10 -
 sw/qa/extras/odfexport/data/listformat.docx                 |binary
 sw/qa/extras/odfexport/data/listformat.odt                  |binary
 sw/qa/extras/odfexport/odfexport.cxx                        |    2 
 sw/qa/extras/odfexport/odfexport2.cxx                       |   66 ++++++++++++
 sw/qa/extras/ooxmlexport/ooxmlexport14.cxx                  |    8 -
 sw/qa/extras/rtfexport/rtfexport.cxx                        |    2 
 sw/qa/extras/ww8export/ww8export2.cxx                       |   18 ---
 sw/qa/extras/ww8export/ww8export3.cxx                       |    6 -
 sw/qa/python/check_cross_references.py                      |   21 ++-
 sw/source/core/doc/number.cxx                               |   14 --
 sw/source/filter/ww8/wrtw8num.cxx                           |    2 
 sw/source/filter/ww8/ww8par3.cxx                            |    4 
 sw/source/ui/misc/outline.cxx                               |    4 
 writerfilter/source/dmapper/NumberingManager.cxx            |   14 +-
 xmloff/source/core/xmltoken.cxx                             |    1 
 xmloff/source/style/xmlnume.cxx                             |   24 +---
 xmloff/source/style/xmlnumi.cxx                             |   27 ++++
 xmloff/source/token/tokens.txt                              |    1 
 26 files changed, 251 insertions(+), 116 deletions(-)

New commits:
commit 9987b518fca1476bd0ce8c86bcf6ac7c81f7b580
Author:     Vasily Melenchuk <vasily.melenc...@cib.de>
AuthorDate: Mon Jun 14 14:27:56 2021 +0300
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Tue Jun 29 19:02:20 2021 +0200

    new ODF numbered list parameter loext:num-list-format
    
    Instead of style:num-prefix and style:num-suffix new list format
    is much more flexible for storing list multilevel numberings.
    Now it is possible to have not just prefix/suffix but any random
    separators between levels, arbitrary levels order, etc.
    
    Internal LO format for list format is changed: instead of placeholders
    like %1, %2, etc we right now use %1%, %2%... Reason: for ODT documents,
    having more than 9 levels there is ambiguity in "%10": it is "%1"
    followed by "0" suffix, or "%10"?
    
    Aux changes:
    * removed zero width space hack: since format string is always defined
      this hack is interfering with standard list numbers printing
      (see changes in ooxmlexport14.cxx, ww8export3.cxx tests)
    * changed cross-references values to lists: they are now including full
      list label string: previously this was bit self-contradictory (see
      changes in odfexport.cxx and check_cross_references.py tests)
    
    Conflicts:
          sw/qa/extras/odfexport/odfexport.cxx
    
    Change-Id: I9696cc4846375c5f6222539aeaadbca5ae58ce27
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117156
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118040
    Reviewed-by: Vasily Melenchuk <vasily.melenc...@cib.de>

diff --git a/cui/source/tabpages/numpages.cxx b/cui/source/tabpages/numpages.cxx
index fbeb40aa4b3d..32063c6f1c5d 100644
--- a/cui/source/tabpages/numpages.cxx
+++ b/cui/source/tabpages/numpages.cxx
@@ -314,14 +314,8 @@ IMPL_LINK_NOARG(SvxSingleNumPickTabPage, 
NumSelectHdl_Impl, ValueSet*, void)
         {
             SvxNumberFormat aFmt(pActNum->GetLevel(i));
             aFmt.SetNumberingType(eNewType);
-            if(cLocalPrefix == ' ')
-                aFmt.SetPrefix( "" );
-            else
-                aFmt.SetPrefix(_pSet->sPrefix);
-            if(cLocalSuffix == ' ')
-                aFmt.SetSuffix( "" );
-            else
-                aFmt.SetSuffix(_pSet->sSuffix);
+            aFmt.SetListFormat(cLocalPrefix == ' ' ? "" : _pSet->sPrefix,
+                               cLocalSuffix == ' ' ? "" : _pSet->sSuffix, i);
             aFmt.SetCharFormatName("");
             aFmt.SetBulletRelSize(100);
             pActNum->SetLevel(i, aFmt);
@@ -459,8 +453,7 @@ IMPL_LINK_NOARG(SvxBulletPickTabPage, NumSelectHdl_Impl, 
ValueSet*, void)
             SvxNumberFormat aFmt(pActNum->GetLevel(i));
             aFmt.SetNumberingType( SVX_NUM_CHAR_SPECIAL );
             // #i93908# clear suffix for bullet lists
-            aFmt.SetPrefix( OUString() );
-            aFmt.SetSuffix( OUString() );
+            aFmt.SetListFormat("", "", i);
             aFmt.SetBulletFont(&rActBulletFont);
             aFmt.SetBulletChar(cChar );
             aFmt.SetCharFormatName(sBulletCharFormatName);
@@ -652,8 +645,7 @@ IMPL_LINK_NOARG(SvxNumPickTabPage, NumSelectHdl_Impl, 
ValueSet*, void)
         if(aFmt.GetNumberingType() == SVX_NUM_CHAR_SPECIAL)
         {
             // #i93908# clear suffix for bullet lists
-            aFmt.SetPrefix(OUString());
-            aFmt.SetSuffix(OUString());
+            aFmt.SetListFormat("", "", i);
             if( !pLevelSettings->sBulletFont.isEmpty() &&
                 pLevelSettings->sBulletFont != rActBulletFont.GetFamilyName())
             {
@@ -702,8 +694,7 @@ IMPL_LINK_NOARG(SvxNumPickTabPage, NumSelectHdl_Impl, 
ValueSet*, void)
             aFmt.SetCharFormatName(sNumCharFmtName);
             aFmt.SetBulletRelSize(100);
             // #i93908#
-            aFmt.SetPrefix(pLevelSettings->sPrefix);
-            aFmt.SetSuffix(pLevelSettings->sSuffix);
+            aFmt.SetListFormat(pLevelSettings->sPrefix, 
pLevelSettings->sSuffix, i);
         }
         pActNum->SetLevel(i, aFmt);
     }
@@ -885,8 +876,7 @@ IMPL_LINK_NOARG(SvxBitmapPickTabPage, NumSelectHdl_Impl, 
ValueSet*, void)
         {
             SvxNumberFormat aFmt(pActNum->GetLevel(i));
             aFmt.SetNumberingType(SVX_NUM_BITMAP);
-            aFmt.SetPrefix( "" );
-            aFmt.SetSuffix( "" );
+            aFmt.SetListFormat("", "", i);
             aFmt.SetCharFormatName( "" );
 
             Graphic aGraphic;
@@ -1644,8 +1634,7 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, 
weld::ComboBox&, rBox,
             {
                 bBmp |= nullptr != aNumFmt.GetBrush();
                 aNumFmt.SetIncludeUpperLevels( 0 );
-                aNumFmt.SetSuffix( "" );
-                aNumFmt.SetPrefix( "" );
+                aNumFmt.SetListFormat("", "", i);
                 if(!bBmp)
                     aNumFmt.SetGraphic("");
                 pActNum->SetLevel(i, aNumFmt);
@@ -1655,8 +1644,7 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, 
weld::ComboBox&, rBox,
             else if( SVX_NUM_CHAR_SPECIAL == nNumberingType )
             {
                 aNumFmt.SetIncludeUpperLevels( 0 );
-                aNumFmt.SetSuffix( "" );
-                aNumFmt.SetPrefix( "" );
+                aNumFmt.SetListFormat("", "", i);
                 if( !aNumFmt.GetBulletFont() )
                     aNumFmt.SetBulletFont(&aActBulletFont);
                 if( !aNumFmt.GetBulletChar() )
@@ -1671,8 +1659,8 @@ IMPL_LINK(SvxNumOptionsTabPage, NumberTypeSelectHdl_Impl, 
weld::ComboBox&, rBox,
             }
             else
             {
-                aNumFmt.SetPrefix( m_xPrefixED->get_text() );
-                aNumFmt.SetSuffix( m_xSuffixED->get_text() );
+                aNumFmt.SetListFormat(m_xPrefixED->get_text(), 
m_xSuffixED->get_text(), i);
+
                 SwitchNumberType(SHOW_NUMBERING);
                 pActNum->SetLevel(i, aNumFmt);
                 CheckForStartValue_Impl(nNumberingType);
@@ -2098,8 +2086,7 @@ IMPL_LINK(SvxNumOptionsTabPage, SpinModifyHdl_Impl, 
weld::SpinButton&, rSpinButt
 
 void SvxNumOptionsTabPage::EditModifyHdl_Impl(const weld::Entry* pEdit)
 {
-    bool bPrefix = pEdit == m_xPrefixED.get();
-    bool bSuffix = pEdit == m_xSuffixED.get();
+    bool bPrefixSuffix = (pEdit == m_xPrefixED.get())|| (pEdit == 
m_xSuffixED.get());
     bool bStart = pEdit == m_xStartED.get();
     sal_uInt16 nMask = 1;
     for(sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++)
@@ -2107,10 +2094,8 @@ void SvxNumOptionsTabPage::EditModifyHdl_Impl(const 
weld::Entry* pEdit)
         if(nActNumLvl & nMask)
         {
             SvxNumberFormat aNumFmt(pActNum->GetLevel(i));
-            if(bPrefix)
-                aNumFmt.SetPrefix(m_xPrefixED->get_text());
-            else if(bSuffix)
-                aNumFmt.SetSuffix(m_xSuffixED->get_text());
+            if (bPrefixSuffix)
+                aNumFmt.SetListFormat(m_xPrefixED->get_text(), 
m_xSuffixED->get_text(), i);
             else if(bStart)
                 aNumFmt.SetStart(m_xStartED->get_value());
             pActNum->SetLevel(i, aNumFmt);
diff --git a/editeng/source/items/numitem.cxx b/editeng/source/items/numitem.cxx
index 2dd03a1877cf..9c55ef1e401c 100644
--- a/editeng/source/items/numitem.cxx
+++ b/editeng/source/items/numitem.cxx
@@ -560,6 +560,55 @@ OUString SvxNumberFormat::CreateRomanString( sal_Int32 
nNo, bool bUpper )
     return sRet.makeStringAndClear();
 }
 
+void SvxNumberFormat::SetListFormat(const OUString& rPrefix, const OUString& 
rSuffix, int nLevel)
+{
+    sPrefix = rPrefix;
+    sSuffix = rSuffix;
+
+    // Generate list format
+    sListFormat = std::make_optional(sPrefix);
+
+    for (int i = 1; i <= nInclUpperLevels; i++)
+    {
+        int nLevelId = nLevel - nInclUpperLevels + i;
+        if (nLevelId < 0)
+            // There can be cases with curent level 1, but request to show 10 
upper levels. Trim it
+            continue;
+
+        *sListFormat += "%";
+        *sListFormat += OUString::number(nLevelId + 1);
+        *sListFormat += "%";
+        if (i != nInclUpperLevels)
+            *sListFormat += "."; // Default separator for older ODT
+    }
+
+    *sListFormat += sSuffix;
+}
+
+void SvxNumberFormat::SetListFormat(std::optional<OUString> oSet)
+{
+    sPrefix.clear();
+    sSuffix.clear();
+
+    if (!oSet.has_value())
+    {
+        return;
+    }
+
+    sListFormat = oSet;
+
+    // For backward compatibility and UI we should create prefix/suffix also
+    sal_Int32 nFirstReplacement = sListFormat->indexOf('%');
+    sal_Int32 nLastReplacement = sListFormat->lastIndexOf('%') + 1;
+    if (nFirstReplacement > 0)
+        // Everything before first '%' will be prefix
+        sPrefix = sListFormat->copy(0, nFirstReplacement);
+    if (nLastReplacement >= 0 && nLastReplacement < sListFormat->getLength())
+        // Everything beyond last '%' is a suffix
+        sSuffix = sListFormat->copy(nLastReplacement);
+}
+
+
 OUString SvxNumberFormat::GetCharFormatName()const
 {
     return sCharStyleName;
diff --git a/include/editeng/numitem.hxx b/include/editeng/numitem.hxx
index b4b9e030fb2d..f955ea15d008 100644
--- a/include/editeng/numitem.hxx
+++ b/include/editeng/numitem.hxx
@@ -171,7 +171,9 @@ public:
     const OUString& GetPrefix() const { return sPrefix;}
     void            SetSuffix(const OUString& rSet) { sSuffix = rSet;}
     const OUString& GetSuffix() const { return sSuffix;}
-    void            SetListFormat(std::optional<OUString> oSet = std::nullopt) 
{ sListFormat = oSet; }
+    // Based on prefix and suffix ininialize them (for backward compatibility) 
and generate listformat string
+    void            SetListFormat(const OUString& rPrefix, const OUString& 
rSuffix, int nLevel);
+    void            SetListFormat(std::optional<OUString> oSet = std::nullopt);
     bool            HasListFormat() const { return sListFormat.has_value(); }
     const OUString& GetListFormat() const { return *sListFormat; }
 
diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx
index 68efdb41ee02..8179d03ba11c 100644
--- a/include/xmloff/xmltoken.hxx
+++ b/include/xmloff/xmltoken.hxx
@@ -1370,6 +1370,7 @@ namespace xmloff::token {
         XML_NULL_YEAR,
         XML_NUM_FORMAT,
         XML_NUM_LETTER_SYNC,
+        XML_NUM_LIST_FORMAT,
         XML_NUM_PREFIX,
         XML_NUM_SUFFIX,
         XML_NUMALIGN,
diff --git a/offapi/com/sun/star/style/NumberingLevel.idl 
b/offapi/com/sun/star/style/NumberingLevel.idl
index eb3cb92add03..16402da791d4 100644
--- a/offapi/com/sun/star/style/NumberingLevel.idl
+++ b/offapi/com/sun/star/style/NumberingLevel.idl
@@ -41,10 +41,14 @@ published service NumberingLevel
     [property] short ParentNumbering;
 
     /** This prefix is inserted in front of the numbering symbol(s).
+
+        @deprecated as of LibreOffice 7.2, use ListFormat instead
      */
     [property] string Prefix;
 
     /** This suffix is inserted after the numbering symbol(s).
+
+        @deprecated as of LibreOffice 7.2, use ListFormat instead
      */
     [property] string Suffix;
 
@@ -81,6 +85,23 @@ published service NumberingLevel
         @since LibreOffice 6.1
      */
     [optional, property] com::sun::star::awt::XBitmap GraphicBitmap;
+
+    /** Format string used to generate actual numbering.
+
+        It contains placeholders (like %1%, %2%, etc) where corresponding
+        level numberings are inserted.
+
+        This is more flexible way to provide multilevel numbering with
+        complex format string. This property is a replacement for
+        Prefix and Suffix: if ListFormat is provided, they are not used
+        anymore.
+
+        Example: ListFormat "(%1% %2%.%3%)" can be resolved to numbering
+        in actual multilevel list like "(4 1.3)".
+
+        @since LibreOffice 7.2
+     */
+    [optional, property] string ListFormat;
 };
 
 
diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng 
b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
index 69a98498e3a4..2800a3eac028 100644
--- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
+++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng
@@ -2660,4 +2660,13 @@ 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.
     </rng:optional>
   </rng:define>
 
+  <!-- https://issues.oasis-open.org/browse/OFFICE-4108 -->
+  <rng:define name="common-num-format-prefix-suffix-attlist" 
combine="interleave">
+    <rng:optional>
+      <rng:attribute name="loext:num-list-format">
+        <rng:ref name="string"/>
+      </rng:attribute>
+    </rng:optional>
+  </rng:define>
+
 </rng:grammar>
diff --git a/sd/source/ui/dlg/BulletAndPositionDlg.cxx 
b/sd/source/ui/dlg/BulletAndPositionDlg.cxx
index fb798956e4ef..fe6ecf9ee52a 100644
--- a/sd/source/ui/dlg/BulletAndPositionDlg.cxx
+++ b/sd/source/ui/dlg/BulletAndPositionDlg.cxx
@@ -716,8 +716,7 @@ IMPL_LINK(SvxBulletAndPositionDlg, 
NumberTypeSelectHdl_Impl, weld::ComboBox&, rB
             {
                 bBmp |= nullptr != aNumFmt.GetBrush();
                 aNumFmt.SetIncludeUpperLevels(0);
-                aNumFmt.SetSuffix("");
-                aNumFmt.SetPrefix("");
+                aNumFmt.SetListFormat("", "", i);
                 if (!bBmp)
                     aNumFmt.SetGraphic("");
                 pActNum->SetLevel(i, aNumFmt);
@@ -726,8 +725,7 @@ IMPL_LINK(SvxBulletAndPositionDlg, 
NumberTypeSelectHdl_Impl, weld::ComboBox&, rB
             else if (SVX_NUM_CHAR_SPECIAL == nNumberingType)
             {
                 aNumFmt.SetIncludeUpperLevels(0);
-                aNumFmt.SetSuffix("");
-                aNumFmt.SetPrefix("");
+                aNumFmt.SetListFormat("", "", i);
                 if (!aNumFmt.GetBulletFont())
                     aNumFmt.SetBulletFont(&aActBulletFont);
                 if (!aNumFmt.GetBulletChar())
@@ -738,8 +736,7 @@ IMPL_LINK(SvxBulletAndPositionDlg, 
NumberTypeSelectHdl_Impl, weld::ComboBox&, rB
             }
             else
             {
-                aNumFmt.SetPrefix(m_xPrefixED->get_text());
-                aNumFmt.SetSuffix(m_xSuffixED->get_text());
+                aNumFmt.SetListFormat(m_xPrefixED->get_text(), 
m_xSuffixED->get_text(), i);
                 SwitchNumberType(SHOW_NUMBERING);
                 pActNum->SetLevel(i, aNumFmt);
                 CheckForStartValue_Impl(nNumberingType);
@@ -1233,8 +1230,7 @@ IMPL_LINK(SvxBulletAndPositionDlg, RelativeHdl_Impl, 
weld::Toggleable&, rBox, vo
 
 void SvxBulletAndPositionDlg::EditModifyHdl_Impl(const weld::Entry* pEdit)
 {
-    bool bPrefix = pEdit == m_xPrefixED.get();
-    bool bSuffix = pEdit == m_xSuffixED.get();
+    bool bPrefixOrSuffix = (pEdit == m_xPrefixED.get()) || (pEdit == 
m_xSuffixED.get());
     bool bStart = pEdit == m_xStartED.get();
     sal_uInt16 nMask = 1;
     for (sal_uInt16 i = 0; i < pActNum->GetLevelCount(); i++)
@@ -1242,10 +1238,8 @@ void SvxBulletAndPositionDlg::EditModifyHdl_Impl(const 
weld::Entry* pEdit)
         if (nActNumLvl & nMask)
         {
             SvxNumberFormat aNumFmt(pActNum->GetLevel(i));
-            if (bPrefix)
-                aNumFmt.SetPrefix(m_xPrefixED->get_text());
-            else if (bSuffix)
-                aNumFmt.SetSuffix(m_xSuffixED->get_text());
+            if (bPrefixOrSuffix)
+                aNumFmt.SetListFormat(m_xPrefixED->get_text(), 
m_xSuffixED->get_text(), i);
             else if (bStart)
                 aNumFmt.SetStart(m_xStartED->get_value());
             pActNum->SetLevel(i, aNumFmt);
diff --git a/svx/source/sidebar/nbdtmg.cxx b/svx/source/sidebar/nbdtmg.cxx
index 06a7ea4d207f..cddac0a5f823 100644
--- a/svx/source/sidebar/nbdtmg.cxx
+++ b/svx/source/sidebar/nbdtmg.cxx
@@ -340,8 +340,7 @@ void BulletsTypeMgr::ApplyNumRule(SvxNumRule& aNum, 
sal_uInt16 nIndex, sal_uInt1
             aFmt.SetBulletFont(&rActBulletFont);
             aFmt.SetBulletChar(cChar);
             aFmt.SetCharFormatName(sBulletCharFormatName);
-            aFmt.SetPrefix( "" );
-            aFmt.SetSuffix( "" );
+            aFmt.SetListFormat( "" );
             if (isResetSize) aFmt.SetBulletRelSize(45);
             aNum.SetLevel(i, aFmt);
         }
@@ -524,9 +523,7 @@ void NumberingTypeMgr::ApplyNumRule(SvxNumRule& aNum, 
sal_uInt16 nIndex, sal_uIn
             SvxNumberFormat aFmt(aNum.GetLevel(i));
             if (eNewType!=aFmt.GetNumberingType()) isResetSize=true;
             aFmt.SetNumberingType(eNewType);
-            aFmt.SetPrefix(_pSet->pNumSetting->sPrefix);
-            aFmt.SetSuffix(_pSet->pNumSetting->sSuffix);
-
+            aFmt.SetListFormat(_pSet->pNumSetting->sPrefix, 
_pSet->pNumSetting->sSuffix, i);
             aFmt.SetCharFormatName(sNumCharFmtName);
             if (isResetSize) aFmt.SetBulletRelSize(100);
             aNum.SetLevel(i, aFmt);
@@ -872,8 +869,7 @@ void OutlineTypeMgr::ApplyNumRule(SvxNumRule& aNum, 
sal_uInt16 nIndex, sal_uInt1
             aFmt.SetFirstLineIndent(pLevelSettings->nNumAlignAt);
             aFmt.SetIndentAt(pLevelSettings->nNumIndentAt);
         }
-        aFmt.SetPrefix(pLevelSettings->sPrefix);
-        aFmt.SetSuffix(pLevelSettings->sSuffix);
+        aFmt.SetListFormat(pLevelSettings->sPrefix, pLevelSettings->sSuffix, 
i);
         aNum.SetLevel(i, aFmt);
     }
 }
diff --git a/sw/qa/extras/odfexport/data/listformat.docx 
b/sw/qa/extras/odfexport/data/listformat.docx
new file mode 100644
index 000000000000..338678d82d3f
Binary files /dev/null and b/sw/qa/extras/odfexport/data/listformat.docx differ
diff --git a/sw/qa/extras/odfexport/data/listformat.odt 
b/sw/qa/extras/odfexport/data/listformat.odt
new file mode 100644
index 000000000000..ec3992c8fde9
Binary files /dev/null and b/sw/qa/extras/odfexport/data/listformat.odt differ
diff --git a/sw/qa/extras/odfexport/odfexport.cxx 
b/sw/qa/extras/odfexport/odfexport.cxx
index 4f6e3b21c2e0..83a5b0cafbe3 100644
--- a/sw/qa/extras/odfexport/odfexport.cxx
+++ b/sw/qa/extras/odfexport/odfexport.cxx
@@ -2697,7 +2697,7 @@ DECLARE_ODFEXPORT_TEST(testReferenceLanguage, 
"referencelanguage.odt")
     const char* aFieldTexts[] = { "A 2", "Az Isten", "Az 50-esek",
         "A 2018-asok", "Az egyebek", "A fejezetek",
         reinterpret_cast<char const *>(u8"Az „Őseinket...”"), "a 2",
-        "Az v", "az 1", "Az e", "az 1",
+        "Az v.", "az 1", "Az e)", "az 1",
         "Az (5)", "az 1", "A 2", "az 1" };
     uno::Reference<text::XTextFieldsSupplier> xTextFieldsSupplier(mxComponent, 
uno::UNO_QUERY);
     // update "A (4)" to "Az (5)"
diff --git a/sw/qa/extras/odfexport/odfexport2.cxx 
b/sw/qa/extras/odfexport/odfexport2.cxx
index b58e9e9a1d1a..bbe5d7f193e7 100644
--- a/sw/qa/extras/odfexport/odfexport2.cxx
+++ b/sw/qa/extras/odfexport/odfexport2.cxx
@@ -50,6 +50,72 @@ DECLARE_ODFEXPORT_TEST(testTdf137199, "tdf137199.docx")
     CPPUNIT_ASSERT_EQUAL(OUString("HELLO2WORLD!"), 
getProperty<OUString>(getParagraph(4), "ListLabelString"));
 }
 
+DECLARE_ODFEXPORT_TEST(testListFormatDocx, "listformat.docx")
+{
+    // Ensure in resulting ODT we also have not just prefix/suffux, but custom 
delimiters
+    CPPUNIT_ASSERT_EQUAL(OUString(">1<"), 
getProperty<OUString>(getParagraph(1), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1/1<<"), 
getProperty<OUString>(getParagraph(2), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1/1/1<<"), 
getProperty<OUString>(getParagraph(3), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1/1/2<<"), 
getProperty<OUString>(getParagraph(4), "ListLabelString"));
+
+    // Check also that in numbering styles we have num-list-format defined
+    xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='1']", "num-list-format", 
">%1%<");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='2']", "num-list-format", 
">>%1%/%2%<<");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='3']", "num-list-format", 
">>%1%/%2%/%3%<<");
+
+    // But for compatibility there are still prefix/suffix
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='1']", "num-prefix", ">");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='1']", "num-suffix", "<");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='2']", "num-prefix", ">>");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='2']", "num-suffix", "<<");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='3']", "num-prefix", ">>");
+    assertXPath(pXmlDoc, 
"/office:document-styles/office:styles/text:list-style[@style:name='WWNum1']/"
+        "text:list-level-style-number[@text:level='3']", "num-suffix", "<<");
+}
+
+DECLARE_ODFEXPORT_TEST(testListFormatOdt, "listformat.odt")
+{
+    // Ensure in resulting ODT we also have not just prefix/suffux, but custom 
delimiters
+    CPPUNIT_ASSERT_EQUAL(OUString(">1<"), 
getProperty<OUString>(getParagraph(1), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1.1<<"), 
getProperty<OUString>(getParagraph(2), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1.1.1<<"), 
getProperty<OUString>(getParagraph(3), "ListLabelString"));
+    CPPUNIT_ASSERT_EQUAL(OUString(">>1.1.2<<"), 
getProperty<OUString>(getParagraph(4), "ListLabelString"));
+
+    if (xmlDocUniquePtr pXmlDoc = parseExport("content.xml"))
+    {
+        // Check how conversion from prefix/suffix to list format did work
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='1']", 
"num-list-format", ">%1%<");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='2']", 
"num-list-format", ">>%1%.%2%<<");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='3']", 
"num-list-format", ">>%1%.%2%.%3%<<");
+
+        // But for compatibility there are still prefix/suffix as they were 
before
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='1']", "num-prefix", 
">");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='1']", "num-suffix", 
"<");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='2']", "num-prefix", 
">>");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='2']", "num-suffix", 
"<<");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='3']", "num-prefix", 
">>");
+        assertXPath(pXmlDoc, 
"/office:document-content/office:automatic-styles/text:list-style[@style:name='L1']/"
+            "text:list-level-style-number[@text:level='3']", "num-suffix", 
"<<");
+    }
+}
+
 // This test started in LO 7.2. Use the odfexport.cxx if you intend to 
backport to 7.1.
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
index d309e3577b80..e383984c1170 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
@@ -1070,12 +1070,12 @@ DECLARE_OOXMLEXPORT_TEST(testTdf120394, 
"tdf120394.docx")
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(2), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(1), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(3), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(1), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(5), 
uno::UNO_QUERY);
@@ -1090,7 +1090,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf133605, "tdf133605.docx")
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(3), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(0), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(4), 
uno::UNO_QUERY);
@@ -1116,7 +1116,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf133605_2, 
"tdf133605_2.docx")
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(3), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(0), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(4), 
uno::UNO_QUERY);
diff --git a/sw/qa/extras/rtfexport/rtfexport.cxx 
b/sw/qa/extras/rtfexport/rtfexport.cxx
index 75c4382324e5..714aa8f468b1 100644
--- a/sw/qa/extras/rtfexport/rtfexport.cxx
+++ b/sw/qa/extras/rtfexport/rtfexport.cxx
@@ -672,7 +672,7 @@ DECLARE_RTFEXPORT_TEST(testFdo66682, "fdo66682.rtf")
             aListFormat = rProp.Value.get<OUString>();
     }
     // Suffix was '\0' instead of ' '.
-    CPPUNIT_ASSERT_EQUAL(OUString(" %1 "), aListFormat);
+    CPPUNIT_ASSERT_EQUAL(OUString(" %1% "), aListFormat);
 }
 
 DECLARE_RTFEXPORT_TEST(testParaShadow, "para-shadow.rtf")
diff --git a/sw/qa/extras/ww8export/ww8export2.cxx 
b/sw/qa/extras/ww8export/ww8export2.cxx
index a1f8fe239d7a..3dc101b6f626 100644
--- a/sw/qa/extras/ww8export/ww8export2.cxx
+++ b/sw/qa/extras/ww8export/ww8export2.cxx
@@ -387,19 +387,11 @@ DECLARE_WW8EXPORT_TEST(testTdf119232_startEvenPage, 
"tdf119232_startEvenPage.doc
 
 DECLARE_WW8EXPORT_TEST(testTdf104805, "tdf104805.doc")
 {
-    uno::Reference<beans::XPropertySet> 
xPropertySet(getStyles("NumberingStyles")->getByName("WW8Num1"), 
uno::UNO_QUERY);
-    uno::Reference<container::XIndexAccess> 
xLevels(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY);
-    uno::Sequence<beans::PropertyValue> aNumberingRule;
-    xLevels->getByIndex(1) >>= aNumberingRule; // 2nd level
-    for (const auto& rPair : std::as_const(aNumberingRule))
-    {
-        if (rPair.Name == "Prefix")
-            // This was "." instead of empty, so the second paragraph was
-            // rendered as ".1" instead of "1.".
-            CPPUNIT_ASSERT_EQUAL(OUString(), rPair.Value.get<OUString>());
-        else if (rPair.Name == "Suffix")
-            CPPUNIT_ASSERT_EQUAL(OUString("."), rPair.Value.get<OUString>());
-    }
+    // Prefix was "." instead of empty, so the second paragraph was
+    // rendered as ".1" instead of "1.".
+    // Unittest modified due to Prefix/Suffix support obsolete
+    uno::Reference<beans::XPropertySet> xPara(getParagraph(2), uno::UNO_QUERY);
+    CPPUNIT_ASSERT_EQUAL(OUString("1."), getProperty<OUString>(xPara, 
"ListLabelString"));
 }
 
 DECLARE_WW8EXPORT_TEST(testTdf104334, "tdf104334.doc")
diff --git a/sw/qa/extras/ww8export/ww8export3.cxx 
b/sw/qa/extras/ww8export/ww8export3.cxx
index ffdf64cf5f1c..8c523682281b 100644
--- a/sw/qa/extras/ww8export/ww8export3.cxx
+++ b/sw/qa/extras/ww8export/ww8export3.cxx
@@ -794,12 +794,12 @@ DECLARE_WW8EXPORT_TEST(testTdf120394, "tdf120394.doc")
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(5), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(0), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(8), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(2), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(9), 
uno::UNO_QUERY);
@@ -809,7 +809,7 @@ DECLARE_WW8EXPORT_TEST(testTdf120394, "tdf120394.doc")
     {
         uno::Reference<beans::XPropertySet> xPara(getParagraph(10), 
uno::UNO_QUERY);
         CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(2), 
getProperty<sal_Int16>(xPara, "NumberingLevel"));
-        CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty<OUString>(xPara, 
"ListLabelString"));
+        CPPUNIT_ASSERT_EQUAL(OUString(), getProperty<OUString>(xPara, 
"ListLabelString"));
     }
 }
 
diff --git a/sw/qa/python/check_cross_references.py 
b/sw/qa/python/check_cross_references.py
index de51d919c7c8..3c9319200ea7 100644
--- a/sw/qa/python/check_cross_references.py
+++ b/sw/qa/python/check_cross_references.py
@@ -89,15 +89,16 @@ class CheckCrossReferences(unittest.TestCase):
         FieldResult1 = "*i*"
         FieldResult2 = "+b+*i*"
         FieldResult3 = "-1-+b+*i*"
-        FieldResult4 = "1"
-        FieldResult5 = "1"
-        FieldResult6 = "A.1"
-        FieldResult7 = "2(a)"
-        FieldResult8 = "2(b)"
-        FieldResult9 = "2"
-        FieldResult10 = "1(a)"
+        FieldResult4 = "1."
+        FieldResult5 = "1."
+        FieldResult6 = "A.1."
+        FieldResult7 = " 2.(a)"
+        FieldResult8 = " 2.(b)"
+        FieldResult9 = " 2."
+        FieldResult10 = " 1.(a)"
         FieldResult11 = "(b)"
         FieldResult12 = "(a)"
+        FieldResult13 = " 1."
 
         # variables for current field
         xField = self.getNextField()
@@ -155,9 +156,9 @@ class CheckCrossReferences(unittest.TestCase):
 
         xField = self.getNextField()
         xProps = self.getFieldProps(xField)
-        self.checkField(xField, xProps, NUMBER, FieldResult4)
-        self.checkField(xField, xProps, NUMBER_NO_CONTEXT, FieldResult4)
-        self.checkField(xField, xProps, NUMBER_FULL_CONTEXT, FieldResult4)
+        self.checkField(xField, xProps, NUMBER, FieldResult13)
+        self.checkField(xField, xProps, NUMBER_NO_CONTEXT, FieldResult13)
+        self.checkField(xField, xProps, NUMBER_FULL_CONTEXT, FieldResult13)
 
         xField = self.getNextField()
         xProps = self.getFieldProps(xField)
diff --git a/sw/source/core/doc/number.cxx b/sw/source/core/doc/number.cxx
index 00eeed63ec70..c545448cf90c 100644
--- a/sw/source/core/doc/number.cxx
+++ b/sw/source/core/doc/number.cxx
@@ -388,8 +388,8 @@ SwNumRule::SwNumRule( const OUString& rNm,
             pFormat->SetStart( 1 );
             pFormat->SetAbsLSpace( lNumberIndent + SwNumRule::GetNumIndent( n 
) );
             pFormat->SetFirstLineOffset( lNumberFirstLineOffset );
-            pFormat->SetSuffix( "." );
-            pFormat->SetBulletChar( numfunc::GetBulletChar(n));
+            pFormat->SetListFormat("%" + OUString::number(n + 1) + "%.");
+            pFormat->SetBulletChar(numfunc::GetBulletChar(n));
             SwNumRule::saBaseFormats[ NUM_RULE ][ n ] = pFormat;
         }
         // position-and-space mode LABEL_ALIGNMENT
@@ -411,7 +411,7 @@ SwNumRule::SwNumRule( const OUString& rNm,
             pFormat->SetListtabPos( cIndentAt[ n ] );
             pFormat->SetFirstLineIndent( cFirstLineIndent );
             pFormat->SetIndentAt( cIndentAt[ n ] );
-            pFormat->SetSuffix( "." );
+            pFormat->SetListFormat( "%" + OUString::number(n + 1) + "%.");
             pFormat->SetBulletChar( numfunc::GetBulletChar(n));
             SwNumRule::saLabelAlignmentBaseFormats[ NUM_RULE ][ n ] = pFormat;
         }
@@ -682,18 +682,12 @@ OUString SwNumRule::MakeNumString( const 
SwNumberTree::tNumberVector & rNumVecto
                     else
                         sReplacement = "0";        // all 0 level are a 0
 
-                    OUString sFind("%" + OUString::number(i + 1));
+                    OUString sFind("%" + OUString::number(i + 1) + "%");
                     sal_Int32 nPosition = sLevelFormat.indexOf(sFind);
                     if (nPosition >= 0)
                         sLevelFormat = sLevelFormat.replaceAt(nPosition, 
sFind.getLength(), sReplacement);
                 }
 
-                // As a fallback: caller code expects nonempty string as a 
result.
-                // But if we have empty string (and had no errors before) this 
is valid result.
-                // So use classical hack with zero-width-space as a string 
filling.
-                if (sLevelFormat.isEmpty())
-                    sLevelFormat = OUStringChar(CHAR_ZWSP);
-
                 aStr = sLevelFormat;
             }
             else
diff --git a/sw/source/filter/ww8/wrtw8num.cxx 
b/sw/source/filter/ww8/wrtw8num.cxx
index 6cb95976c655..1f77b6d41d34 100644
--- a/sw/source/filter/ww8/wrtw8num.cxx
+++ b/sw/source/filter/ww8/wrtw8num.cxx
@@ -519,7 +519,7 @@ void MSWordExportBase::NumberingLevel(
                 sal_Int32 nFnd = sNumStr.indexOf(sSrch);
                 if (-1 != nFnd)
                 {
-                    *pLvlPos = static_cast<sal_uInt8>(nFnd + 
rFormat.GetPrefix().getLength() + 1);
+                    *pLvlPos = static_cast<sal_uInt8>(nFnd + 1);
                     ++pLvlPos;
                     sNumStr = sNumStr.replaceAt(nFnd, 1, 
OUString(static_cast<char>(i)));
                 }
diff --git a/sw/source/filter/ww8/ww8par3.cxx b/sw/source/filter/ww8/ww8par3.cxx
index 02dd2f139d0b..815a6261fefd 100644
--- a/sw/source/filter/ww8/ww8par3.cxx
+++ b/sw/source/filter/ww8/ww8par3.cxx
@@ -884,7 +884,7 @@ bool WW8ListManager::ReadLVL(SwNumFormat& rNumFormat, 
std::unique_ptr<SfxItemSet
     }
     else
     {
-        // Replace symbols at aOfsNumsXCH offsets to %1, %2 as supported by 
DOCX and LO
+        // Replace symbols at aOfsNumsXCH offsets to %1%, %2% as supported by 
LO
         OUString sListFormat = sNumString;
         if (sListFormat.getLength())
         {
@@ -902,7 +902,7 @@ bool WW8ListManager::ReadLVL(SwNumFormat& rNumFormat, 
std::unique_ptr<SfxItemSet
                 }
                 sal_uInt8 nReplacement = sListFormat[nOffset] + 1;
 
-                OUString sReplacement("%" + OUString::number(nReplacement));
+                OUString sReplacement("%" + OUString::number(nReplacement) + 
"%");
                 sListFormat = sListFormat.replaceAt(nOffset, 1, sReplacement);
 
                 // We need also update an offset, since we are replacing one 
symbol by at least two
diff --git a/sw/source/ui/misc/outline.cxx b/sw/source/ui/misc/outline.cxx
index f01b6386901e..ebac78812a6c 100644
--- a/sw/source/ui/misc/outline.cxx
+++ b/sw/source/ui/misc/outline.cxx
@@ -648,9 +648,7 @@ IMPL_LINK_NOARG(SwOutlineSettingsTabPage, DelimModify, 
weld::Entry&, void)
         if(nActLevel & nMask)
         {
             SwNumFormat aNumFormat(pNumRule->Get(i));
-            aNumFormat.SetPrefix( m_xPrefixED->get_text() );
-            aNumFormat.SetSuffix( m_xSuffixED->get_text() );
-            aNumFormat.SetListFormat();  // clear custom format
+            aNumFormat.SetListFormat( m_xPrefixED->get_text(), 
m_xSuffixED->get_text(), i );
             pNumRule->Set(i, aNumFormat);
         }
         nMask <<= 1;
diff --git a/writerfilter/source/dmapper/NumberingManager.cxx 
b/writerfilter/source/dmapper/NumberingManager.cxx
index f7ee07c9c98c..62706d1a5614 100644
--- a/writerfilter/source/dmapper/NumberingManager.cxx
+++ b/writerfilter/source/dmapper/NumberingManager.cxx
@@ -44,6 +44,7 @@
 #include <comphelper/sequence.hxx>
 #include <comphelper/propertyvalue.hxx>
 #include <comphelper/string.hxx>
+#include <regex>
 
 using namespace com::sun::star;
 
@@ -676,18 +677,19 @@ void ListsManager::lcl_attribute( Id nName, Value& rVal )
     {
         case NS_ooxml::LN_CT_LevelText_val:
         {
-            //this strings contains the definition of the level
-            //the level number is marked as %n
-            //these numbers can be mixed randomly together with separators 
pre- and suffixes
-            //the Writer supports only a number of upper levels to show, 
separators is always a dot
-            //and each level can have a prefix and a suffix
             if(pCurrentLvl)
             {
                 //if the BulletChar is a soft-hyphen (0xad)
                 //replace it with a hard-hyphen (0x2d)
                 //-> this fixes missing hyphen export in PDF etc.
                 // see tdf#101626
-                pCurrentLvl->SetBulletChar( rVal.getString().replace( 0xad, 
0x2d ) );
+                std::string sLevelText = rVal.getString().replace(0xad, 
0x2d).toUtf8().getStr();
+
+                // DOCX level-text contains levels definition in format 
"%1.%2.%3"
+                // we need to convert it to LO internal representation: 
"%1%.%2%.%3%"
+                std::regex aTokenRegex("(%\\d)");
+                sLevelText = std::regex_replace(sLevelText, aTokenRegex, 
"$1%");
+                pCurrentLvl->SetBulletChar( OUString::fromUtf8(sLevelText) );
             }
         }
         break;
diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx
index 4334f52a74ac..27815a3d1ebf 100644
--- a/xmloff/source/core/xmltoken.cxx
+++ b/xmloff/source/core/xmltoken.cxx
@@ -1376,6 +1376,7 @@ namespace xmloff::token {
         TOKEN( "null-year",                       XML_NULL_YEAR ),
         TOKEN( "num-format",                      XML_NUM_FORMAT ),
         TOKEN( "num-letter-sync",                 XML_NUM_LETTER_SYNC ),
+        TOKEN( "num-list-format",                 XML_NUM_LIST_FORMAT ),
         TOKEN( "num-prefix",                      XML_NUM_PREFIX ),
         TOKEN( "num-suffix",                      XML_NUM_SUFFIX ),
         TOKEN( "numalign",                        XML_NUMALIGN ),
diff --git a/xmloff/source/style/xmlnume.cxx b/xmloff/source/style/xmlnume.cxx
index 132e3f701a2e..45e398b5c889 100644
--- a/xmloff/source/style/xmlnume.cxx
+++ b/xmloff/source/style/xmlnume.cxx
@@ -82,7 +82,7 @@ void SvxXMLNumRuleExport::exportLevelStyle( sal_Int32 nLevel,
     sal_Int16 eType = NumberingType::CHAR_SPECIAL;
 
     sal_Int16 eAdjust = HoriOrientation::LEFT;
-    OUString sPrefix, sSuffix;
+    OUString sPrefix, sSuffix, sListFormat;
     OUString sTextStyleName;
     bool bHasColor = false;
     sal_Int32 nColor = 0;
@@ -123,20 +123,7 @@ void SvxXMLNumRuleExport::exportLevelStyle( sal_Int32 
nLevel,
         }
         else if (rProp.Name == "ListFormat")
         {
-            OUString sListFormat;
             rProp.Value >>= sListFormat;
-
-            // Since we have no support for entire format string it should be 
converted
-            // to prefix and suffix. Of course, it is not so flexible as 
format string,
-            // but it is the only option
-            sal_Int32 nFirstReplacement = sListFormat.indexOf('%');
-            sal_Int32 nLastReplacement = sListFormat.lastIndexOf('%') + 1;
-            if (nFirstReplacement > 0)
-                // Everything before first '%' will be prefix
-                sPrefix = sListFormat.copy(0, nFirstReplacement);
-            if (nLastReplacement >= 0 && nLastReplacement  < 
sListFormat.getLength() -1 )
-                // Everything beyond last '%' (+1 for follow up id) is a suffix
-                sSuffix = sListFormat.copy(nLastReplacement + 1);
         }
         else if (rProp.Name == "BulletChar")
         {
@@ -269,6 +256,15 @@ void SvxXMLNumRuleExport::exportLevelStyle( sal_Int32 
nLevel,
             GetExport().AddAttribute( XML_NAMESPACE_TEXT, XML_STYLE_NAME,
                     GetExport().EncodeStyleName( sTextStyleName ) );
         }
+        if (!sListFormat.isEmpty())
+        {
+            if (GetExport().getSaneDefaultVersion() & 
SvtSaveOptions::ODFSVER_EXTENDED)
+            {
+                // Write only in extended mode: in ODF 1.3 we write only 
prefix/suffix,
+                // no list format yet available. Praying we did not lost some 
formatting.
+                GetExport().AddAttribute(XML_NAMESPACE_LO_EXT, 
XML_NUM_LIST_FORMAT, sListFormat);
+            }
+        }
         if (!sPrefix.isEmpty())
         {
             GetExport().AddAttribute( XML_NAMESPACE_STYLE, XML_NUM_PREFIX,
diff --git a/xmloff/source/style/xmlnumi.cxx b/xmloff/source/style/xmlnumi.cxx
index 3f8040bbad6c..335bbfac928f 100644
--- a/xmloff/source/style/xmlnumi.cxx
+++ b/xmloff/source/style/xmlnumi.cxx
@@ -64,6 +64,7 @@
 #include <xmloff/maptype.hxx>
 
 #include <xmloff/xmlnumi.hxx>
+#include <optional>
 
 using namespace ::com::sun::star;
 using namespace ::com::sun::star::uno;
@@ -114,6 +115,8 @@ class SvxXMLListLevelStyleContext_Impl : public 
SvXMLImportContext
 
     OUString            sPrefix;
     OUString            sSuffix;
+    std::optional<OUString> sListFormat;    // It is optional to distinguish 
empty format string
+                                            // from not existing format string 
in old docs
     OUString            sTextStyleName;
     OUString            sNumFormat;
     OUString            sNumLetterSync;
@@ -298,6 +301,10 @@ 
SvxXMLListLevelStyleContext_Impl::SvxXMLListLevelStyleContext_Impl(
         case XML_ELEMENT(STYLE, XML_NUM_SUFFIX):
             sSuffix = aIter.toString();
             break;
+        case XML_ELEMENT(STYLE, XML_NUM_LIST_FORMAT):
+        case XML_ELEMENT(LO_EXT, XML_NUM_LIST_FORMAT):
+            sListFormat = std::make_optional(aIter.toString());
+            break;
         case XML_ELEMENT(STYLE, XML_NUM_LETTER_SYNC):
             if( bNum )
                 sNumLetterSync = aIter.toString();
@@ -392,12 +399,32 @@ Sequence<beans::PropertyValue> 
SvxXMLListLevelStyleContext_Impl::GetProperties()
         }
     }
 
+    if (!sListFormat.has_value())
+    {
+        // This is older document: it has no list format, but can probably 
contain prefix and/or suffix
+        // Generate list format string, based on this
+        sListFormat = std::make_optional(sPrefix);
+
+        for (int i = 1; i <= nNumDisplayLevels; i++)
+        {
+            *sListFormat += "%";
+            *sListFormat += OUString::number(nLevel - nNumDisplayLevels + i + 
1);
+            *sListFormat += "%";
+            if (i != nNumDisplayLevels)
+                *sListFormat += ".";     // Default separator for older ODT
+        }
+
+        *sListFormat += sSuffix;
+    }
+
     aProperties.push_back(comphelper::makePropertyValue("NumberingType", 
eType));
 
     aProperties.push_back(comphelper::makePropertyValue("Prefix", sPrefix));
 
     aProperties.push_back(comphelper::makePropertyValue("Suffix", sSuffix));
 
+    aProperties.push_back(comphelper::makePropertyValue("ListFormat", 
*sListFormat));
+
     aProperties.push_back(comphelper::makePropertyValue("Adjust", eAdjust));
 
     sal_Int32 nLeftMargin = nSpaceBefore + nMinLabelWidth;
diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt
index de859d508fb8..403482cce9ab 100644
--- a/xmloff/source/token/tokens.txt
+++ b/xmloff/source/token/tokens.txt
@@ -1283,6 +1283,7 @@ null-date
 null-year
 num-format
 num-letter-sync
+num-list-format
 num-prefix
 num-suffix
 numalign
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to