From aa5c6d127559912ad60a63fbd972b78fb8f9691b Mon Sep 17 00:00:00 2001 From: Vasily Melenchuk Date: Mon, 14 Jun 2021 14:27:56 +0300 Subject: [PATCH] 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) Change-Id: I9696cc4846375c5f6222539aeaadbca5ae58ce27 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117156 Tested-by: Jenkins Reviewed-by: Michael Stahl --- 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 ++++++ .../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 | Bin 0 -> 13082 bytes sw/qa/extras/odfexport/data/listformat.odt | Bin 0 -> 9221 bytes 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 +- .../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(-) create mode 100644 sw/qa/extras/odfexport/data/listformat.docx create mode 100644 sw/qa/extras/odfexport/data/listformat.odt 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 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 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 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. + + + + + + + + + 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 438639e092a2..76d1004e61dc 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); @@ -875,8 +872,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 0000000000000000000000000000000000000000..338678d82d3f323746ed4583e55c091f4e8ce2d9 GIT binary patch literal 13082 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 literal 0 HcmV?d00001 diff --git a/sw/qa/extras/odfexport/data/listformat.odt b/sw/qa/extras/odfexport/data/listformat.odt new file mode 100644 index 0000000000000000000000000000000000000000..ec3992c8fde93afac4abd1511f91868fa0af0658 GIT binary patch literal 9221 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 literal 0 HcmV?d00001 diff --git a/sw/qa/extras/odfexport/odfexport.cxx b/sw/qa/extras/odfexport/odfexport.cxx index c117591d9ad8..06e675f48ee9 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") OUString const aFieldTexts[] = { "A 2", "Az Isten", "Az 50-esek", "A 2018-asok", "Az egyebek", "A fejezetek", u"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 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(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(getParagraph(1), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1/1<<"), getProperty(getParagraph(2), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1/1/1<<"), getProperty(getParagraph(3), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1/1/2<<"), getProperty(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(getParagraph(1), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1.1<<"), getProperty(getParagraph(2), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1.1.1<<"), getProperty(getParagraph(3), "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(">>1.1.2<<"), getProperty(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 xPara(getParagraph(2), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(1), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference xPara(getParagraph(3), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(1), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference xPara(getParagraph(5), uno::UNO_QUERY); @@ -1090,7 +1090,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf133605, "tdf133605.docx") { uno::Reference xPara(getParagraph(3), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(0), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference xPara(getParagraph(4), uno::UNO_QUERY); @@ -1116,7 +1116,7 @@ DECLARE_OOXMLEXPORT_TEST(testTdf133605_2, "tdf133605_2.docx") { uno::Reference xPara(getParagraph(3), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(0), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference 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(); } // 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 xPropertySet(getStyles("NumberingStyles")->getByName("WW8Num1"), uno::UNO_QUERY); - uno::Reference xLevels(xPropertySet->getPropertyValue("NumberingRules"), uno::UNO_QUERY); - uno::Sequence 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()); - else if (rPair.Name == "Suffix") - CPPUNIT_ASSERT_EQUAL(OUString("."), rPair.Value.get()); - } + // 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 xPara(getParagraph(2), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(OUString("1."), getProperty(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 9ccf3ea19a31..a69ddb7d989d 100644 --- a/sw/qa/extras/ww8export/ww8export3.cxx +++ b/sw/qa/extras/ww8export/ww8export3.cxx @@ -881,12 +881,12 @@ DECLARE_WW8EXPORT_TEST(testTdf120394, "tdf120394.doc") { uno::Reference xPara(getParagraph(5), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(0), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference xPara(getParagraph(8), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(2), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(xPara, "ListLabelString")); } { uno::Reference xPara(getParagraph(9), uno::UNO_QUERY); @@ -896,7 +896,7 @@ DECLARE_WW8EXPORT_TEST(testTdf120394, "tdf120394.doc") { uno::Reference xPara(getParagraph(10), uno::UNO_QUERY); CPPUNIT_ASSERT_EQUAL(static_cast(2), getProperty(xPara, "NumberingLevel")); - CPPUNIT_ASSERT_EQUAL(OUString(CHAR_ZWSP), getProperty(xPara, "ListLabelString")); + CPPUNIT_ASSERT_EQUAL(OUString(), getProperty(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 d2c3f427687b..4522613f525c 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 fc04626bd5d6..1313c79c031f 100644 --- a/sw/source/filter/ww8/wrtw8num.cxx +++ b/sw/source/filter/ww8/wrtw8num.cxx @@ -496,7 +496,7 @@ void MSWordExportBase::NumberingLevel( sal_Int32 nFnd = sNumStr.indexOf(sSrch); if (-1 != nFnd) { - *pLvlPos = static_cast(nFnd + rFormat.GetPrefix().getLength() + 1); + *pLvlPos = static_cast(nFnd + 1); ++pLvlPos; sNumStr = sNumStr.replaceAt(nFnd, 1, OUString(static_cast(i))); } diff --git a/sw/source/filter/ww8/ww8par3.cxx b/sw/source/filter/ww8/ww8par3.cxx index 7c33ea08829a..79c64955ab89 100644 --- a/sw/source/filter/ww8/ww8par3.cxx +++ b/sw/source/filter/ww8/ww8par3.cxx @@ -939,7 +939,7 @@ bool WW8ListManager::ReadLVL(SwNumFormat& rNumFormat, std::unique_ptrGet(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 #include #include +#include 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 6568d3ea9496..63e70ed9ee7b 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 #include +#include 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 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 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